From 440b468e273f337da3740c33b81d671412a5fc64 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Thu, 18 Jan 2024 20:42:08 +0100 Subject: [PATCH 01/98] Some partial success on getting fly to work similarly to SoD --- lib/pathfinder/PathfinderOptions.cpp | 2 +- lib/pathfinder/PathfindingRules.cpp | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/pathfinder/PathfinderOptions.cpp b/lib/pathfinder/PathfinderOptions.cpp index 4c83acafc..a7e42795c 100644 --- a/lib/pathfinder/PathfinderOptions.cpp +++ b/lib/pathfinder/PathfinderOptions.cpp @@ -29,7 +29,7 @@ PathfinderOptions::PathfinderOptions() , useCastleGate(false) , lightweightFlyingMode(false) , oneTurnSpecialLayersLimit(true) - , originalMovementRules(false) + , originalMovementRules(true) , turnLimit(std::numeric_limits::max()) , canUseCast(false) { diff --git a/lib/pathfinder/PathfindingRules.cpp b/lib/pathfinder/PathfindingRules.cpp index d041aff85..ff589f3ee 100644 --- a/lib/pathfinder/PathfindingRules.cpp +++ b/lib/pathfinder/PathfindingRules.cpp @@ -384,12 +384,23 @@ void LayerTransitionRule::process( case EPathfindingLayer::AIR: if(pathfinderConfig->options.originalMovementRules) { - if((source.node->accessible != EPathAccessibility::ACCESSIBLE && - source.node->accessible != EPathAccessibility::VISITABLE) && - (destination.node->accessible != EPathAccessibility::VISITABLE && - destination.node->accessible != EPathAccessibility::ACCESSIBLE)) + if(destination.coord.x == 2 && destination.coord.y == 35) + logGlobal->error(source.node->coord.toString() + std::string(" Layer: ") + std::to_string(destination.node->layer) + std::string(" Accessibility: ") + std::to_string((int)source.node->accessible)); + + if(source.node->accessible != EPathAccessibility::ACCESSIBLE && + source.node->accessible != EPathAccessibility::VISITABLE && + destination.node->accessible != EPathAccessibility::VISITABLE && + destination.node->accessible != EPathAccessibility::ACCESSIBLE) { - destination.blocked = true; + if(destination.node->accessible == EPathAccessibility::BLOCKVIS) + { + if(source.nodeObject || source.tile->blocked) + { + destination.blocked = true; + } + } + else + destination.blocked = true; } } else if(destination.node->accessible != EPathAccessibility::ACCESSIBLE) From c2c43602eab421140726e98d3d1b9fed634b4c13 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 11 Nov 2023 16:43:58 +0200 Subject: [PATCH 02/98] Initial version of global lobby server available in client --- CMakeLists.txt | 11 ++ client/CMakeLists.txt | 4 + client/gui/InterfaceObjectConfigurable.cpp | 3 + client/mainmenu/CMainMenu.cpp | 9 + client/mainmenu/CMainMenu.h | 2 + client/serverLobby/LobbyWindow.cpp | 40 +++++ client/serverLobby/LobbyWindow.h | 37 ++++ cmake_modules/VCMI_lib.cmake | 9 + config/widgets/lobbyWindow.json | 186 +++++++++++++++++++ lib/network/NetworkClient.cpp | 47 +++++ lib/network/NetworkClient.h | 37 ++++ lib/network/NetworkConnection.cpp | 97 ++++++++++ lib/network/NetworkConnection.h | 36 ++++ lib/network/NetworkDefines.h | 22 +++ lib/network/NetworkServer.cpp | 63 +++++++ lib/network/NetworkServer.h | 39 ++++ lobby/CMakeLists.txt | 39 ++++ lobby/LobbyServer.cpp | 45 +++++ lobby/LobbyServer.h | 24 +++ lobby/SQLiteConnection.cpp | 197 +++++++++++++++++++++ lobby/SQLiteConnection.h | 99 +++++++++++ lobby/StdInc.cpp | 2 + lobby/StdInc.h | 14 ++ 23 files changed, 1062 insertions(+) create mode 100644 client/serverLobby/LobbyWindow.cpp create mode 100644 client/serverLobby/LobbyWindow.h create mode 100644 config/widgets/lobbyWindow.json create mode 100644 lib/network/NetworkClient.cpp create mode 100644 lib/network/NetworkClient.h create mode 100644 lib/network/NetworkConnection.cpp create mode 100644 lib/network/NetworkConnection.h create mode 100644 lib/network/NetworkDefines.h create mode 100644 lib/network/NetworkServer.cpp create mode 100644 lib/network/NetworkServer.h create mode 100644 lobby/CMakeLists.txt create mode 100644 lobby/LobbyServer.cpp create mode 100644 lobby/LobbyServer.h create mode 100644 lobby/SQLiteConnection.cpp create mode 100644 lobby/SQLiteConnection.h create mode 100644 lobby/StdInc.cpp create mode 100644 lobby/StdInc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e39a446fd..5cc8db9cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,10 @@ if(NOT APPLE_IOS AND NOT ANDROID) option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF) endif() +if(NOT APPLE_IOS AND NOT ANDROID) + option(ENABLE_LOBBY "Enable compilation of lobby server" ON) +endif() + option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" OFF) if(ENABLE_CCACHE) find_program(CCACHE ccache REQUIRED) @@ -475,6 +479,10 @@ if(TARGET SDL2_ttf::SDL2_ttf) add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf) endif() +if(ENABLE_LOBBY) + find_package(SQLite3 REQUIRED) +endif() + if(ENABLE_LAUNCHER OR ENABLE_EDITOR) # Widgets finds its own dependencies (QtGui and QtCore). find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network) @@ -622,6 +630,9 @@ endif() if(ENABLE_EDITOR) add_subdirectory(mapeditor) endif() +if(ENABLE_LOBBY) + add_subdirectory(lobby) +endif() add_subdirectory(client) add_subdirectory(server) if(ENABLE_TEST) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 20697bc2b..4f8c6aaa1 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -95,6 +95,8 @@ set(client_SRCS renderSDL/ScreenHandler.cpp renderSDL/SDL_Extensions.cpp + serverLobby/LobbyWindow.cpp + widgets/Buttons.cpp widgets/CArtifactHolder.cpp widgets/CComponent.cpp @@ -270,6 +272,8 @@ set(client_HEADERS renderSDL/SDL_Extensions.h renderSDL/SDL_PixelAccess.h + serverLobby/LobbyWindow.h + widgets/Buttons.h widgets/CArtifactHolder.h widgets/CComponent.h diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index 27383365d..beb9a55d4 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -129,6 +129,9 @@ void InterfaceObjectConfigurable::build(const JsonNode &config) for(const auto & item : items->Vector()) addWidget(item["name"].String(), buildWidget(item)); + + pos.w = config["width"].Integer(); + pos.h = config["height"].Integer(); } void InterfaceObjectConfigurable::addConditional(const std::string & name, bool active) diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 1d867d308..058d2cd8e 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -24,6 +24,7 @@ #include "../gui/Shortcut.h" #include "../gui/WindowHandler.h" #include "../render/Canvas.h" +#include "../serverLobby/LobbyWindow.h" #include "../widgets/CComponent.h" #include "../widgets/Buttons.h" #include "../widgets/MiscWidgets.h" @@ -459,9 +460,17 @@ CMultiMode::CMultiMode(ESelectionScreen ScreenType) buttonHotseat = std::make_shared(Point(373, 78), AnimationPath::builtin("MUBHOT.DEF"), CGI->generaltexth->zelp[266], std::bind(&CMultiMode::hostTCP, this)); buttonHost = std::make_shared(Point(373, 78 + 57 * 1), AnimationPath::builtin("MUBHOST.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.hostTCP"), ""), std::bind(&CMultiMode::hostTCP, this)); buttonJoin = std::make_shared(Point(373, 78 + 57 * 2), AnimationPath::builtin("MUBJOIN.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.joinTCP"), ""), std::bind(&CMultiMode::joinTCP, this)); + buttonLobby = std::make_shared(Point(373, 78 + 57 * 4), AnimationPath::builtin("MUBONL.DEF"), CGI->generaltexth->zelp[265], std::bind(&CMultiMode::openLobby, this)); + buttonCancel = std::make_shared(Point(373, 424), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[288], [=](){ close();}, EShortcut::GLOBAL_CANCEL); } +void CMultiMode::openLobby() +{ + close(); + GH.windows().createAndPushWindow(); +} + void CMultiMode::hostTCP() { auto savedScreenType = screenType; diff --git a/client/mainmenu/CMainMenu.h b/client/mainmenu/CMainMenu.h index ea9010797..ed2cdf585 100644 --- a/client/mainmenu/CMainMenu.h +++ b/client/mainmenu/CMainMenu.h @@ -86,12 +86,14 @@ public: std::shared_ptr picture; std::shared_ptr playerName; std::shared_ptr buttonHotseat; + std::shared_ptr buttonLobby; std::shared_ptr buttonHost; std::shared_ptr buttonJoin; std::shared_ptr buttonCancel; std::shared_ptr statusBar; CMultiMode(ESelectionScreen ScreenType); + void openLobby(); void hostTCP(); void joinTCP(); std::string getPlayerName(); diff --git a/client/serverLobby/LobbyWindow.cpp b/client/serverLobby/LobbyWindow.cpp new file mode 100644 index 000000000..635542213 --- /dev/null +++ b/client/serverLobby/LobbyWindow.cpp @@ -0,0 +1,40 @@ +/* + * LobbyWindow.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "LobbyWindow.h" + +#include "../gui/CGuiHandler.h" +#include "../gui/WindowHandler.h" + +void LobbyClient::onPacketReceived(const std::vector & message) +{ + +} + +LobbyWidget::LobbyWidget() +{ + addCallback("closeWindow", [](int) { GH.windows().popWindows(1); }); + + const JsonNode config(JsonPath::builtin("config/widgets/lobbyWindow.json")); + build(config); +} + +LobbyWindow::LobbyWindow(): + CWindowObject(BORDERED) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + widget = std::make_shared(); + pos = widget->pos; + center(); + connection = std::make_shared(); + + connection->start("127.0.0.1", 30303); +} diff --git a/client/serverLobby/LobbyWindow.h b/client/serverLobby/LobbyWindow.h new file mode 100644 index 000000000..592004b5f --- /dev/null +++ b/client/serverLobby/LobbyWindow.h @@ -0,0 +1,37 @@ +/* + * LobbyWindow.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../gui/InterfaceObjectConfigurable.h" +#include "../windows/CWindowObject.h" + +#include "../../lib/network/NetworkClient.h" + +class LobbyWidget : public InterfaceObjectConfigurable +{ +public: + LobbyWidget(); +}; + +class LobbyClient : public NetworkClient +{ + void onPacketReceived(const std::vector & message) override; +public: + LobbyClient() = default; +}; + +class LobbyWindow : public CWindowObject +{ + std::shared_ptr widget; + std::shared_ptr connection; + +public: + LobbyWindow(); +}; diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 2709974d3..a9a9f8c74 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -124,6 +124,10 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/modding/IdentifierStorage.cpp ${MAIN_LIB_DIR}/modding/ModUtility.cpp + ${MAIN_LIB_DIR}/network/NetworkClient.cpp + ${MAIN_LIB_DIR}/network/NetworkConnection.cpp + ${MAIN_LIB_DIR}/network/NetworkServer.cpp + ${MAIN_LIB_DIR}/networkPacks/NetPacksLib.cpp ${MAIN_LIB_DIR}/pathfinder/CGPathNode.cpp @@ -471,6 +475,11 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/modding/ModUtility.h ${MAIN_LIB_DIR}/modding/ModVerificationInfo.h + ${MAIN_LIB_DIR}/network/NetworkClient.h + ${MAIN_LIB_DIR}/network/NetworkConnection.h + ${MAIN_LIB_DIR}/network/NetworkDefines.h + ${MAIN_LIB_DIR}/network/NetworkServer.h + ${MAIN_LIB_DIR}/networkPacks/ArtifactLocation.h ${MAIN_LIB_DIR}/networkPacks/BattleChanges.h ${MAIN_LIB_DIR}/networkPacks/Component.h diff --git a/config/widgets/lobbyWindow.json b/config/widgets/lobbyWindow.json new file mode 100644 index 000000000..672cf34dd --- /dev/null +++ b/config/widgets/lobbyWindow.json @@ -0,0 +1,186 @@ +{ + "customTypes" : { + "labelTitleMain" : { + "type": "label", + "font": "big", + "alignment": "left", + "color": "yellow" + }, + "labelTitle" : { + "type": "label", + "font": "small", + "alignment": "left", + "color": "yellow" + }, + "backgroundTexture" : { + "type": "texture", + "font": "tiny", + "color" : "blue", + "image": "DIBOXBCK" + }, + "areaFilled":{ + "type": "transparentFilledRectangle", + "color": [0, 0, 0, 75], + "colorLine": [64, 80, 128, 255] + } + }, + + + "width": 1024, + "height": 600, + + "items": + [ + { + "type": "backgroundTexture", + "rect": {"w": 1024, "h": 600} + }, + + { + "type": "areaFilled", + "rect": {"x": 5, "y": 5, "w": 250, "h": 40} + }, + { + "type": "labelTitleMain", + "position": {"x": 15, "y": 10}, + "text" : "Player Name" + }, + + { + "type": "areaFilled", + "rect": {"x": 5, "y": 50, "w": 250, "h": 150} + }, + { + "type": "labelTitle", + "position": {"x": 15, "y": 53}, + "text" : "Match Filter" + }, + + { + "type": "areaFilled", + "rect": {"x": 5, "y": 210, "w": 250, "h": 310} + }, + { + "type": "labelTitle", + "position": {"x": 15, "y": 213}, + "text" : "Match List" + }, + + { + "type": "areaFilled", + "rect": {"x": 270, "y": 50, "w": 150, "h": 540} + }, + { + "type": "labelTitle", + "position": {"x": 280, "y": 53}, + "text" : "Channel List" + }, + + { + "type": "areaFilled", + "rect": {"x": 430, "y": 50, "w": 430, "h": 515} + }, + { + "type": "labelTitle", + "position": {"x": 440, "y": 53}, + "text" : "Game Chat" + }, + + { + "type": "areaFilled", + "rect": {"x": 430, "y": 565, "w": 395, "h": 25} + }, + { + "type": "labelTitle", + "position": {"x": 440, "y": 568}, + "text" : "Enter Message" + }, + + { + "type": "areaFilled", + "rect": {"x": 870, "y": 50, "w": 150, "h": 540} + }, + { + "type": "labelTitle", + "position": {"x": 880, "y": 53}, + "text" : "Player List" + }, + + { + "type": "button", + "position": {"x": 940, "y": 10}, + "image": "settingsWindow/button80", + "help": "core.help.288", + "callback": "closeWindow", + "hotkey": "globalReturn", + "items": + [ + { + "type": "label", + "font": "medium", + "alignment": "center", + "color": "yellow", + "text": "Exit" + } + ] + }, + + { + "type": "button", + "position": {"x": 828, "y": 565}, + "image": "settingsWindow/button32", + "help": "core.help.288", + "callback": "closeWindow", + "hotkey": "globalReturn", + "items": + [ + { + "type": "label", + "font": "medium", + "alignment": "center", + "color": "yellow", + "text": ">" + } + ] + }, + + { + "type": "button", + "position": {"x": 10, "y": 520}, + "image": "settingsWindow/button190", + "help": "core.help.288", + "callback": "closeWindow", + "hotkey": "globalReturn", + "items": + [ + { + "type": "label", + "font": "medium", + "alignment": "center", + "color": "yellow", + "text": "Start Public Game" + } + ] + }, + + { + "type": "button", + "position": {"x": 10, "y": 555}, + "image": "settingsWindow/button190", + "help": "core.help.288", + "callback": "closeWindow", + "hotkey": "globalReturn", + "items": + [ + { + "type": "label", + "font": "medium", + "alignment": "center", + "color": "yellow", + "text": "Start Private Game" + } + ] + }, + + ] +} diff --git a/lib/network/NetworkClient.cpp b/lib/network/NetworkClient.cpp new file mode 100644 index 000000000..717939b8e --- /dev/null +++ b/lib/network/NetworkClient.cpp @@ -0,0 +1,47 @@ +/* + * NetworkClient.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "NetworkClient.h" +#include "NetworkConnection.h" + +VCMI_LIB_NAMESPACE_BEGIN + +NetworkClient::NetworkClient() + : io(new NetworkService) + , socket(new NetworkSocket(*io)) +// , timer(new NetworkTimer(*io)) +{ +} + +void NetworkClient::start(const std::string & host, uint16_t port) +{ + boost::asio::ip::tcp::resolver resolver(*io); + auto endpoints = resolver.resolve(host, std::to_string(port)); + + boost::asio::async_connect(*socket, endpoints, std::bind(&NetworkClient::onConnected, this, _1)); +} + +void NetworkClient::onConnected(const boost::system::error_code & ec) +{ + connection = std::make_shared(socket); + connection->start(); +} + +void NetworkClient::run() +{ + io->run(); +} + +void NetworkClient::sendPacket(const std::vector & message) +{ + connection->sendPacket(message); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkClient.h b/lib/network/NetworkClient.h new file mode 100644 index 000000000..a28809a9a --- /dev/null +++ b/lib/network/NetworkClient.h @@ -0,0 +1,37 @@ +/* + * NetworkClient.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "NetworkDefines.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class NetworkConnection; + +class DLL_LINKAGE NetworkClient : boost::noncopyable +{ + std::shared_ptr io; + std::shared_ptr socket; + std::shared_ptr connection; + std::shared_ptr timer; + + void onConnected(const boost::system::error_code & ec); +protected: + virtual void onPacketReceived(const std::vector & message) = 0; +public: + NetworkClient(); + virtual ~NetworkClient() = default; + + void start(const std::string & host, uint16_t port); + void sendPacket(const std::vector & message); + void run(); +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp new file mode 100644 index 000000000..eb0565cad --- /dev/null +++ b/lib/network/NetworkConnection.cpp @@ -0,0 +1,97 @@ +/* + * NetworkConnection.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "NetworkConnection.h" + +VCMI_LIB_NAMESPACE_BEGIN + +NetworkConnection::NetworkConnection(const std::shared_ptr & socket) + : socket(socket) +{ + +} + +void NetworkConnection::start() +{ + boost::asio::async_read(*socket, + readBuffer, + boost::asio::transfer_exactly(messageHeaderSize), + std::bind(&NetworkConnection::onHeaderReceived,this, _1)); +} + +void NetworkConnection::onHeaderReceived(const boost::system::error_code & ec) +{ + uint32_t messageSize = readPacketSize(ec); + + boost::asio::async_read(*socket, + readBuffer, + boost::asio::transfer_exactly(messageSize), + std::bind(&NetworkConnection::onPacketReceived,this, _1, messageSize)); +} + +uint32_t NetworkConnection::readPacketSize(const boost::system::error_code & ec) +{ + if (ec) + { + throw std::runtime_error("Connection aborted!"); + } + + if (readBuffer.size() < messageHeaderSize) + { + throw std::runtime_error("Failed to read header!"); + } + + std::istream istream(&readBuffer); + + uint32_t messageSize; + istream.read(reinterpret_cast(&messageSize), messageHeaderSize); + + if (messageSize > messageMaxSize) + { + throw std::runtime_error("Invalid packet size!"); + } + + return messageSize; +} + +void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, uint32_t expectedPacketSize) +{ + if (ec) + { + throw std::runtime_error("Connection aborted!"); + } + + if (readBuffer.size() < expectedPacketSize) + { + throw std::runtime_error("Failed to read header!"); + } + + std::vector message; + + message.resize(expectedPacketSize); + std::istream istream(&readBuffer); + istream.read(reinterpret_cast(message.data()), messageHeaderSize); + + start(); +} + +void NetworkConnection::sendPacket(const std::vector & message) +{ + NetworkBuffer writeBuffer; + + std::ostream ostream(&writeBuffer); + uint32_t messageSize = message.size(); + ostream.write(reinterpret_cast(&messageSize), messageHeaderSize); + ostream.write(reinterpret_cast(message.data()), message.size()); + + boost::asio::write(*socket, writeBuffer ); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h new file mode 100644 index 000000000..d9894bed1 --- /dev/null +++ b/lib/network/NetworkConnection.h @@ -0,0 +1,36 @@ +/* + * NetworkConnection.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "NetworkDefines.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class DLL_LINKAGE NetworkConnection : boost::noncopyable +{ + static const int messageHeaderSize = sizeof(uint32_t); + static const int messageMaxSize = 1024; + + std::shared_ptr socket; + + NetworkBuffer readBuffer; + + void onHeaderReceived(const boost::system::error_code & ec); + void onPacketReceived(const boost::system::error_code & ec, uint32_t expectedPacketSize); + uint32_t readPacketSize(const boost::system::error_code & ec); + +public: + NetworkConnection(const std::shared_ptr & socket); + + void start(); + void sendPacket(const std::vector & message); +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkDefines.h b/lib/network/NetworkDefines.h new file mode 100644 index 000000000..bbf5dc152 --- /dev/null +++ b/lib/network/NetworkDefines.h @@ -0,0 +1,22 @@ +/* + * NetworkDefines.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +using NetworkService = boost::asio::io_service; +using NetworkSocket = boost::asio::basic_stream_socket; +using NetworkAcceptor = boost::asio::basic_socket_acceptor; +using NetworkBuffer = boost::asio::streambuf; +using NetworkTimer = boost::asio::steady_timer; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp new file mode 100644 index 000000000..8aa6b70e6 --- /dev/null +++ b/lib/network/NetworkServer.cpp @@ -0,0 +1,63 @@ +/* + * NetworkServer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "NetworkServer.h" +#include "NetworkConnection.h" + +VCMI_LIB_NAMESPACE_BEGIN + +void NetworkServer::start(uint16_t port) +{ + io = std::make_shared(); + acceptor = std::make_shared(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)); + + startAsyncAccept(); +} + +void NetworkServer::startAsyncAccept() +{ + std::shared_ptr upcomingConnection = std::make_shared(*io); + acceptor->async_accept(*upcomingConnection, std::bind(&NetworkServer::connectionAccepted, this, upcomingConnection, _1)); +} + +void NetworkServer::run() +{ + io->run(); +} + +void NetworkServer::connectionAccepted(std::shared_ptr upcomingConnection, const boost::system::error_code & ec) +{ + if(ec) + { + logNetwork->info("Something wrong during accepting: %s", ec.message()); + return; + } + + try + { + logNetwork->info("We got a new connection! :)"); + auto connection = std::make_shared(upcomingConnection); + connections.insert(connection); + connection->start(); + } + catch(std::exception & e) + { + logNetwork->error("Failure processing new connection! %s", e.what()); + } + + startAsyncAccept(); +} + +void NetworkServer::sendPacket(const std::shared_ptr & connection, const std::vector & message) +{ + connection->sendPacket(message); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkServer.h b/lib/network/NetworkServer.h new file mode 100644 index 000000000..5853cb7ac --- /dev/null +++ b/lib/network/NetworkServer.h @@ -0,0 +1,39 @@ +/* + * NetworkServer.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "NetworkDefines.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class NetworkConnection; + +class DLL_LINKAGE NetworkServer : boost::noncopyable +{ + std::shared_ptr io; + std::shared_ptr acceptor; + std::set> connections; + + void connectionAccepted(std::shared_ptr, const boost::system::error_code & ec); + void startAsyncAccept(); +protected: + virtual void onNewConnection(std::shared_ptr) = 0; + virtual void onPacketReceived(std::shared_ptr, const std::vector & message) = 0; + + void sendPacket(const std::shared_ptr &, const std::vector & message); + +public: + virtual ~NetworkServer() = default; + + void start(uint16_t port); + void run(); +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lobby/CMakeLists.txt b/lobby/CMakeLists.txt new file mode 100644 index 000000000..60ce2ffba --- /dev/null +++ b/lobby/CMakeLists.txt @@ -0,0 +1,39 @@ +set(lobby_SRCS + StdInc.cpp + LobbyServer.cpp + SQLiteConnection.cpp +) + +set(lobby_HEADERS + StdInc.h + LobbyServer.h + SQLiteConnection.h +) + +assign_source_group(${lobby_SRCS} ${lobby_HEADERS}) + +add_executable(vcmilobby ${lobby_SRCS} ${lobby_HEADERS}) +set(lobby_LIBS vcmi) + +if(CMAKE_SYSTEM_NAME MATCHES FreeBSD OR HAIKU) + set(lobby_LIBS execinfo ${lobby_LIBS}) +endif() + +target_link_libraries(vcmilobby PRIVATE ${lobby_LIBS} ${SQLite3_LIBRARIES}) + +target_include_directories(vcmilobby PRIVATE ${SQLite3_INCLUDE_DIRS}) +target_include_directories(vcmilobby PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +if(WIN32) + set_target_properties(vcmilobby + PROPERTIES + OUTPUT_NAME "VCMI_lobby" + PROJECT_LABEL "VCMI_lobby" + ) +endif() + +vcmi_set_output_dir(vcmilobby "") +enable_pch(vcmilobby) + +install(TARGETS vcmilobby DESTINATION ${BIN_DIR}) + diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp new file mode 100644 index 000000000..81648d9ce --- /dev/null +++ b/lobby/LobbyServer.cpp @@ -0,0 +1,45 @@ +/* + * LobbyServer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "LobbyServer.h" + +#include "SQLiteConnection.h" + +#include +#include + +static const std::string DATABASE_PATH = "~/vcmi.db"; +static const int LISTENING_PORT = 30303; +//static const std::string SERVER_NAME = GameConstants::VCMI_VERSION + " (server)"; +//static const std::string SERVER_UUID = boost::uuids::to_string(boost::uuids::random_generator()()); + +void LobbyServer::onNewConnection(std::shared_ptr) +{ + +} + +void LobbyServer::onPacketReceived(std::shared_ptr, const std::vector & message) +{ + +} + +LobbyServer::LobbyServer() +{ + database = SQLiteInstance::open(DATABASE_PATH, true); + +} + +int main(int argc, const char * argv[]) +{ + LobbyServer server; + + server.start(LISTENING_PORT); + server.run(); +} diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h new file mode 100644 index 000000000..c3a434a74 --- /dev/null +++ b/lobby/LobbyServer.h @@ -0,0 +1,24 @@ +/* + * LobbyServer.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../lib/network/NetworkServer.h" + +class SQLiteInstance; + +class LobbyServer : public NetworkServer +{ + std::unique_ptr database; + + void onNewConnection(std::shared_ptr) override; + void onPacketReceived(std::shared_ptr, const std::vector & message) override; +public: + LobbyServer(); +}; diff --git a/lobby/SQLiteConnection.cpp b/lobby/SQLiteConnection.cpp new file mode 100644 index 000000000..cac16ccb2 --- /dev/null +++ b/lobby/SQLiteConnection.cpp @@ -0,0 +1,197 @@ +/* + * SQLiteConnection.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "SQLiteConnection.h" + +#include + +static void on_sqlite_error( sqlite3 * connection, [[maybe_unused]] int result ) +{ + if ( result != SQLITE_OK ) + { + printf( "sqlite error: %s\n", sqlite3_errmsg( connection ) ); + } + + assert( result == SQLITE_OK ); +} + +SQLiteStatement::SQLiteStatement( SQLiteInstance & instance, sqlite3_stmt * statement ): + m_instance( instance ), + m_statement( statement ) +{ } + +SQLiteStatement::~SQLiteStatement( ) +{ + int result = sqlite3_finalize( m_statement ); + on_sqlite_error( m_instance.m_connection, result ); +} + +bool SQLiteStatement::execute( ) +{ + int result = sqlite3_step( m_statement ); + + switch ( result ) + { + case SQLITE_DONE: + return false; + case SQLITE_ROW: + return true; + default: + on_sqlite_error( m_instance.m_connection, result ); + return false; + } +} + +void SQLiteStatement::reset( ) +{ + int result = sqlite3_reset( m_statement ); + on_sqlite_error( m_instance.m_connection, result ); +} + +void SQLiteStatement::clear() +{ + int result = sqlite3_clear_bindings(m_statement); + on_sqlite_error(m_instance.m_connection, result); +} + +void SQLiteStatement::setBindSingle( size_t index, double const & value ) +{ + int result = sqlite3_bind_double( m_statement, static_cast(index), value ); + on_sqlite_error( m_instance.m_connection, result ); +} + +void SQLiteStatement::setBindSingle(size_t index, uint8_t const & value) +{ + int result = sqlite3_bind_int(m_statement, static_cast(index), value); + on_sqlite_error(m_instance.m_connection, result); +} + +void SQLiteStatement::setBindSingle(size_t index, uint16_t const & value) +{ + int result = sqlite3_bind_int(m_statement, static_cast(index), value); + on_sqlite_error(m_instance.m_connection, result); +} +void SQLiteStatement::setBindSingle(size_t index, uint32_t const & value) +{ + int result = sqlite3_bind_int(m_statement, static_cast(index), value); + on_sqlite_error(m_instance.m_connection, result); +} + +void SQLiteStatement::setBindSingle( size_t index, int32_t const & value ) +{ + int result = sqlite3_bind_int( m_statement, static_cast( index ), value ); + on_sqlite_error( m_instance.m_connection, result ); +} + +void SQLiteStatement::setBindSingle( size_t index, int64_t const & value ) +{ + int result = sqlite3_bind_int64( m_statement, static_cast( index ), value ); + on_sqlite_error( m_instance.m_connection, result ); +} + +void SQLiteStatement::setBindSingle( size_t index, std::string const & value ) +{ + int result = sqlite3_bind_text( m_statement, static_cast( index ), value.data(), static_cast( value.size() ), SQLITE_STATIC ); + on_sqlite_error( m_instance.m_connection, result ); +} + +void SQLiteStatement::setBindSingle( size_t index, char const * value ) +{ + int result = sqlite3_bind_text( m_statement, static_cast( index ), value, -1, SQLITE_STATIC ); + on_sqlite_error( m_instance.m_connection, result ); +} + +void SQLiteStatement::getColumnSingle( size_t index, double & value ) +{ + value = sqlite3_column_double( m_statement, static_cast( index ) ); +} + +void SQLiteStatement::getColumnSingle( size_t index, uint8_t & value ) +{ + value = static_cast(sqlite3_column_int( m_statement, static_cast( index ) )); +} + +void SQLiteStatement::getColumnSingle(size_t index, uint16_t & value) +{ + value = static_cast(sqlite3_column_int(m_statement, static_cast(index))); +} + +void SQLiteStatement::getColumnSingle( size_t index, int32_t & value ) +{ + value = sqlite3_column_int( m_statement, static_cast( index ) ); +} + +void SQLiteStatement::getColumnSingle(size_t index, uint32_t & value) +{ + value = sqlite3_column_int(m_statement, static_cast(index)); +} + +void SQLiteStatement::getColumnSingle( size_t index, int64_t & value ) +{ + value = sqlite3_column_int64( m_statement, static_cast( index ) ); +} + +void SQLiteStatement::getColumnSingle( size_t index, std::string & value ) +{ + auto value_raw = sqlite3_column_text(m_statement, static_cast(index)); + value = reinterpret_cast( value_raw ); +} + +void SQLiteStatement::getColumnBlob(size_t index, std::byte * value, [[maybe_unused]] size_t capacity) +{ + auto * blob_data = sqlite3_column_blob(m_statement, static_cast(index)); + size_t blob_size = sqlite3_column_bytes(m_statement, static_cast(index)); + + assert(blob_size < capacity); + + std::copy_n(static_cast(blob_data), blob_size, value); + value[blob_size] = std::byte(0); +} + +SQLiteInstancePtr SQLiteInstance::open( std::string const & db_path, bool allow_write) +{ + int flags = allow_write ? (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) : SQLITE_OPEN_READONLY; + + sqlite3 * connection; + int result = sqlite3_open_v2( db_path.c_str( ), &connection, flags, nullptr ); + + on_sqlite_error( connection, result ); + + assert(result == SQLITE_OK); + + if ( result == SQLITE_OK ) + return SQLiteInstancePtr( new SQLiteInstance( connection ) ); + + sqlite3_close( connection ); + return SQLiteInstancePtr( ); +} + +SQLiteInstance::SQLiteInstance( sqlite3 * connection ): + m_connection( connection ) +{ } + +SQLiteInstance::~SQLiteInstance( ) +{ + int result = sqlite3_close( m_connection ); + on_sqlite_error( m_connection, result ); +} + +SQLiteStatementPtr SQLiteInstance::prepare( std::string const & sql_text ) +{ + sqlite3_stmt * statement; + int result = sqlite3_prepare_v2( m_connection, sql_text.data(), static_cast( sql_text.size()), &statement, nullptr ); + + on_sqlite_error( m_connection, result ); + + if ( result == SQLITE_OK ) + return SQLiteStatementPtr( new SQLiteStatement( *this, statement ) ); + sqlite3_finalize( statement ); + return SQLiteStatementPtr( ); +} diff --git a/lobby/SQLiteConnection.h b/lobby/SQLiteConnection.h new file mode 100644 index 000000000..2cfae0f63 --- /dev/null +++ b/lobby/SQLiteConnection.h @@ -0,0 +1,99 @@ +/* + * SQLiteConnection.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +typedef struct sqlite3 sqlite3; +typedef struct sqlite3_stmt sqlite3_stmt; + +class SQLiteInstance; +class SQLiteStatement; + +using SQLiteInstancePtr = std::unique_ptr< SQLiteInstance >; +using SQLiteStatementPtr = std::unique_ptr< SQLiteStatement >; + +class SQLiteStatement : boost::noncopyable +{ +public: + friend class SQLiteInstance; + + bool execute( ); + void reset( ); + void clear( ); + + ~SQLiteStatement(); + + template + void setBinds( Args const & ... args ) + { + setBindSingle( 1, args... ); // The leftmost SQL parameter has an index of 1 + } + + template + void getColumns( Args & ... args ) + { + getColumnSingle( 0, args... ); // The leftmost column of the result set has the index 0 + } + +private: + void setBindSingle( size_t index, double const & value ); + void setBindSingle( size_t index, uint8_t const & value ); + void setBindSingle( size_t index, uint16_t const & value ); + void setBindSingle( size_t index, uint32_t const & value ); + void setBindSingle( size_t index, int32_t const & value ); + void setBindSingle( size_t index, int64_t const & value ); + void setBindSingle( size_t index, std::string const & value ); + void setBindSingle( size_t index, char const * value ); + + void getColumnSingle( size_t index, double & value ); + void getColumnSingle( size_t index, uint8_t & value ); + void getColumnSingle( size_t index, uint16_t & value ); + void getColumnSingle( size_t index, uint32_t & value ); + void getColumnSingle( size_t index, int32_t & value ); + void getColumnSingle( size_t index, int64_t & value ); + void getColumnSingle( size_t index, std::string & value ); + + SQLiteStatement( SQLiteInstance & instance, sqlite3_stmt * statement ); + + template + void setBindSingle( size_t index, T const & arg, Args const & ... args ) + { + setBindSingle( index, arg ); + setBindSingle( index + 1, args... ); + } + + template + void getColumnSingle( size_t index, T & arg, Args & ... args ) + { + getColumnSingle( index, arg ); + getColumnSingle( index + 1, args... ); + } + + void getColumnBlob(size_t index, std::byte * value, size_t capacity); + + SQLiteInstance & m_instance; + sqlite3_stmt * m_statement; +}; + +class SQLiteInstance : boost::noncopyable +{ +public: + friend class SQLiteStatement; + + static SQLiteInstancePtr open(std::string const & db_path, bool allow_write ); + + ~SQLiteInstance( ); + + SQLiteStatementPtr prepare( std::string const & statement ); + +private: + SQLiteInstance( sqlite3 * connection ); + + sqlite3 * m_connection; +}; diff --git a/lobby/StdInc.cpp b/lobby/StdInc.cpp new file mode 100644 index 000000000..dd7f66cb8 --- /dev/null +++ b/lobby/StdInc.cpp @@ -0,0 +1,2 @@ +// Creates the precompiled header +#include "StdInc.h" diff --git a/lobby/StdInc.h b/lobby/StdInc.h new file mode 100644 index 000000000..d03216bdf --- /dev/null +++ b/lobby/StdInc.h @@ -0,0 +1,14 @@ +/* + * StdInc.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../Global.h" + +VCMI_LIB_USING_NAMESPACE From dff9cf39c063cad6a04b202a3cdc7fb31e7df52b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 12 Nov 2023 13:27:22 +0200 Subject: [PATCH 03/98] Implemented connection handling --- client/serverLobby/LobbyWindow.cpp | 21 +++++++++++++++++++++ client/serverLobby/LobbyWindow.h | 4 ++++ lib/network/NetworkClient.cpp | 24 +++++++++++++++++++++++- lib/network/NetworkClient.h | 10 +++++++++- lib/network/NetworkConnection.cpp | 26 ++++++++++++++------------ lib/network/NetworkConnection.h | 7 ++++--- lib/network/NetworkDefines.h | 11 +++++++++++ lib/network/NetworkServer.cpp | 25 +++++++++++-------------- lib/network/NetworkServer.h | 7 ++++--- lobby/LobbyServer.cpp | 7 +++---- lobby/LobbyServer.h | 4 ++-- 11 files changed, 106 insertions(+), 40 deletions(-) diff --git a/client/serverLobby/LobbyWindow.cpp b/client/serverLobby/LobbyWindow.cpp index 635542213..ce7a41e67 100644 --- a/client/serverLobby/LobbyWindow.cpp +++ b/client/serverLobby/LobbyWindow.cpp @@ -14,11 +14,25 @@ #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" +#include "../windows/InfoWindows.h" + void LobbyClient::onPacketReceived(const std::vector & message) { } +void LobbyClient::onConnectionFailed(const std::string & errorMessage) +{ + GH.windows().popWindows(1); + CInfoWindow::showInfoDialog("Failed to connect to game lobby!\n" + errorMessage, {}); +} + +void LobbyClient::onDisconnected() +{ + GH.windows().popWindows(1); + CInfoWindow::showInfoDialog("Connection to game lobby was lost!", {}); +} + LobbyWidget::LobbyWidget() { addCallback("closeWindow", [](int) { GH.windows().popWindows(1); }); @@ -37,4 +51,11 @@ LobbyWindow::LobbyWindow(): connection = std::make_shared(); connection->start("127.0.0.1", 30303); + + addUsedEvents(TIME); +} + +void LobbyWindow::tick(uint32_t msPassed) +{ + connection->poll(); } diff --git a/client/serverLobby/LobbyWindow.h b/client/serverLobby/LobbyWindow.h index 592004b5f..d13c1d1a6 100644 --- a/client/serverLobby/LobbyWindow.h +++ b/client/serverLobby/LobbyWindow.h @@ -23,6 +23,8 @@ public: class LobbyClient : public NetworkClient { void onPacketReceived(const std::vector & message) override; + void onConnectionFailed(const std::string & errorMessage) override; + void onDisconnected() override; public: LobbyClient() = default; }; @@ -32,6 +34,8 @@ class LobbyWindow : public CWindowObject std::shared_ptr widget; std::shared_ptr connection; + void tick(uint32_t msPassed); + public: LobbyWindow(); }; diff --git a/lib/network/NetworkClient.cpp b/lib/network/NetworkClient.cpp index 717939b8e..d46b0ac2e 100644 --- a/lib/network/NetworkClient.cpp +++ b/lib/network/NetworkClient.cpp @@ -30,7 +30,13 @@ void NetworkClient::start(const std::string & host, uint16_t port) void NetworkClient::onConnected(const boost::system::error_code & ec) { - connection = std::make_shared(socket); + if (ec) + { + onConnectionFailed(ec.message()); + return; + } + + connection = std::make_shared(socket, *this); connection->start(); } @@ -39,9 +45,25 @@ void NetworkClient::run() io->run(); } +void NetworkClient::poll() +{ + io->poll(); +} + void NetworkClient::sendPacket(const std::vector & message) { connection->sendPacket(message); } +void NetworkClient::onDisconnected(const std::shared_ptr & connection) +{ + onDisconnected(); +} + +void NetworkClient::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) +{ + onPacketReceived(message); +} + + VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkClient.h b/lib/network/NetworkClient.h index a28809a9a..5cc87a1ec 100644 --- a/lib/network/NetworkClient.h +++ b/lib/network/NetworkClient.h @@ -15,7 +15,7 @@ VCMI_LIB_NAMESPACE_BEGIN class NetworkConnection; -class DLL_LINKAGE NetworkClient : boost::noncopyable +class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionListener { std::shared_ptr io; std::shared_ptr socket; @@ -23,8 +23,15 @@ class DLL_LINKAGE NetworkClient : boost::noncopyable std::shared_ptr timer; void onConnected(const boost::system::error_code & ec); + + void onDisconnected(const std::shared_ptr & connection) override; + void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; + protected: virtual void onPacketReceived(const std::vector & message) = 0; + virtual void onConnectionFailed(const std::string & errorMessage) = 0; + virtual void onDisconnected() = 0; + public: NetworkClient(); virtual ~NetworkClient() = default; @@ -32,6 +39,7 @@ public: void start(const std::string & host, uint16_t port); void sendPacket(const std::vector & message); void run(); + void poll(); }; VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index eb0565cad..4c0a26471 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -12,8 +12,9 @@ VCMI_LIB_NAMESPACE_BEGIN -NetworkConnection::NetworkConnection(const std::shared_ptr & socket) +NetworkConnection::NetworkConnection(const std::shared_ptr & socket, INetworkConnectionListener & listener) : socket(socket) + , listener(listener) { } @@ -28,7 +29,13 @@ void NetworkConnection::start() void NetworkConnection::onHeaderReceived(const boost::system::error_code & ec) { - uint32_t messageSize = readPacketSize(ec); + if (ec) + { + listener.onDisconnected(shared_from_this()); + return; + } + + uint32_t messageSize = readPacketSize(); boost::asio::async_read(*socket, readBuffer, @@ -36,23 +43,15 @@ void NetworkConnection::onHeaderReceived(const boost::system::error_code & ec) std::bind(&NetworkConnection::onPacketReceived,this, _1, messageSize)); } -uint32_t NetworkConnection::readPacketSize(const boost::system::error_code & ec) +uint32_t NetworkConnection::readPacketSize() { - if (ec) - { - throw std::runtime_error("Connection aborted!"); - } - if (readBuffer.size() < messageHeaderSize) - { throw std::runtime_error("Failed to read header!"); - } std::istream istream(&readBuffer); uint32_t messageSize; istream.read(reinterpret_cast(&messageSize), messageHeaderSize); - if (messageSize > messageMaxSize) { throw std::runtime_error("Invalid packet size!"); @@ -65,7 +64,8 @@ void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, u { if (ec) { - throw std::runtime_error("Connection aborted!"); + listener.onDisconnected(shared_from_this()); + return; } if (readBuffer.size() < expectedPacketSize) @@ -79,6 +79,8 @@ void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, u std::istream istream(&readBuffer); istream.read(reinterpret_cast(message.data()), messageHeaderSize); + listener.onPacketReceived(shared_from_this(), message); + start(); } diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h index d9894bed1..97b1a7f9a 100644 --- a/lib/network/NetworkConnection.h +++ b/lib/network/NetworkConnection.h @@ -13,7 +13,7 @@ VCMI_LIB_NAMESPACE_BEGIN -class DLL_LINKAGE NetworkConnection : boost::noncopyable +class DLL_LINKAGE NetworkConnection :public std::enable_shared_from_this, boost::noncopyable { static const int messageHeaderSize = sizeof(uint32_t); static const int messageMaxSize = 1024; @@ -21,13 +21,14 @@ class DLL_LINKAGE NetworkConnection : boost::noncopyable std::shared_ptr socket; NetworkBuffer readBuffer; + INetworkConnectionListener & listener; void onHeaderReceived(const boost::system::error_code & ec); void onPacketReceived(const boost::system::error_code & ec, uint32_t expectedPacketSize); - uint32_t readPacketSize(const boost::system::error_code & ec); + uint32_t readPacketSize(); public: - NetworkConnection(const std::shared_ptr & socket); + NetworkConnection(const std::shared_ptr & socket, INetworkConnectionListener & listener); void start(); void sendPacket(const std::vector & message); diff --git a/lib/network/NetworkDefines.h b/lib/network/NetworkDefines.h index bbf5dc152..8b05370bb 100644 --- a/lib/network/NetworkDefines.h +++ b/lib/network/NetworkDefines.h @@ -19,4 +19,15 @@ using NetworkAcceptor = boost::asio::basic_socket_acceptor using NetworkBuffer = boost::asio::streambuf; using NetworkTimer = boost::asio::steady_timer; +class NetworkConnection; + +class DLL_LINKAGE INetworkConnectionListener +{ + friend class NetworkConnection; + +protected: + virtual void onDisconnected(const std::shared_ptr & connection) = 0; + virtual void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) = 0; +}; + VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp index 8aa6b70e6..7e201c83f 100644 --- a/lib/network/NetworkServer.cpp +++ b/lib/network/NetworkServer.cpp @@ -36,22 +36,13 @@ void NetworkServer::connectionAccepted(std::shared_ptr upcomingCo { if(ec) { - logNetwork->info("Something wrong during accepting: %s", ec.message()); - return; - } - - try - { - logNetwork->info("We got a new connection! :)"); - auto connection = std::make_shared(upcomingConnection); - connections.insert(connection); - connection->start(); - } - catch(std::exception & e) - { - logNetwork->error("Failure processing new connection! %s", e.what()); + throw std::runtime_error("Something wrong during accepting: " + ec.message()); } + logNetwork->info("We got a new connection! :)"); + auto connection = std::make_shared(upcomingConnection, *this); + connections.insert(connection); + connection->start(); startAsyncAccept(); } @@ -60,4 +51,10 @@ void NetworkServer::sendPacket(const std::shared_ptr & connec connection->sendPacket(message); } +void NetworkServer::onDisconnected(const std::shared_ptr & connection) +{ + assert(connections.count(connection)); + connections.erase(connection); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkServer.h b/lib/network/NetworkServer.h index 5853cb7ac..54cb7c8cd 100644 --- a/lib/network/NetworkServer.h +++ b/lib/network/NetworkServer.h @@ -15,7 +15,7 @@ VCMI_LIB_NAMESPACE_BEGIN class NetworkConnection; -class DLL_LINKAGE NetworkServer : boost::noncopyable +class DLL_LINKAGE NetworkServer : boost::noncopyable, public INetworkConnectionListener { std::shared_ptr io; std::shared_ptr acceptor; @@ -23,9 +23,10 @@ class DLL_LINKAGE NetworkServer : boost::noncopyable void connectionAccepted(std::shared_ptr, const boost::system::error_code & ec); void startAsyncAccept(); + + void onDisconnected(const std::shared_ptr & connection) override; protected: - virtual void onNewConnection(std::shared_ptr) = 0; - virtual void onPacketReceived(std::shared_ptr, const std::vector & message) = 0; + virtual void onNewConnection(const std::shared_ptr &) = 0; void sendPacket(const std::shared_ptr &, const std::vector & message); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 81648d9ce..cdbc20a58 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -15,17 +15,17 @@ #include #include -static const std::string DATABASE_PATH = "~/vcmi.db"; +static const std::string DATABASE_PATH = "/home/ivan/vcmi.db"; static const int LISTENING_PORT = 30303; //static const std::string SERVER_NAME = GameConstants::VCMI_VERSION + " (server)"; //static const std::string SERVER_UUID = boost::uuids::to_string(boost::uuids::random_generator()()); -void LobbyServer::onNewConnection(std::shared_ptr) +void LobbyServer::onNewConnection(const std::shared_ptr &) { } -void LobbyServer::onPacketReceived(std::shared_ptr, const std::vector & message) +void LobbyServer::onPacketReceived(const std::shared_ptr &, const std::vector & message) { } @@ -33,7 +33,6 @@ void LobbyServer::onPacketReceived(std::shared_ptr, const std LobbyServer::LobbyServer() { database = SQLiteInstance::open(DATABASE_PATH, true); - } int main(int argc, const char * argv[]) diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index c3a434a74..329d831b5 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -17,8 +17,8 @@ class LobbyServer : public NetworkServer { std::unique_ptr database; - void onNewConnection(std::shared_ptr) override; - void onPacketReceived(std::shared_ptr, const std::vector & message) override; + void onNewConnection(const std::shared_ptr &) override; + void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; public: LobbyServer(); }; From f10b6df9894645a74c9f7391d404669712337b27 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 12 Nov 2023 15:32:54 +0200 Subject: [PATCH 04/98] Implemented messages sending and storing in database --- client/serverLobby/LobbyWindow.cpp | 46 ++++++++++++++++++++++-- client/serverLobby/LobbyWindow.h | 18 ++++++++-- config/widgets/lobbyWindow.json | 26 ++++++++------ lib/network/NetworkClient.h | 2 +- lib/network/NetworkConnection.cpp | 2 +- lobby/LobbyServer.cpp | 58 +++++++++++++++++++++++++++++- lobby/LobbyServer.h | 17 ++++++++- lobby/SQLiteConnection.cpp | 3 +- 8 files changed, 152 insertions(+), 20 deletions(-) diff --git a/client/serverLobby/LobbyWindow.cpp b/client/serverLobby/LobbyWindow.cpp index ce7a41e67..7f670b58d 100644 --- a/client/serverLobby/LobbyWindow.cpp +++ b/client/serverLobby/LobbyWindow.cpp @@ -13,9 +13,16 @@ #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" +#include "../widgets/TextControls.h" #include "../windows/InfoWindows.h" +LobbyClient::LobbyClient(LobbyWindow * window) + : window(window) +{ + +} + void LobbyClient::onPacketReceived(const std::vector & message) { @@ -33,22 +40,42 @@ void LobbyClient::onDisconnected() CInfoWindow::showInfoDialog("Connection to game lobby was lost!", {}); } -LobbyWidget::LobbyWidget() +void LobbyClient::sendMessage(const JsonNode & data) +{ + std::string payloadString = data.toJson(true); + + // FIXME: find better approach + uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); + uint8_t * payloadEnd = payloadBegin + payloadString.size(); + + std::vector payloadBuffer(payloadBegin, payloadEnd); + + sendPacket(payloadBuffer); +} + +LobbyWidget::LobbyWidget(LobbyWindow * window) + : window(window) { addCallback("closeWindow", [](int) { GH.windows().popWindows(1); }); + addCallback("sendMessage", [this](int) { this->window->doSendChatMessage(); }); const JsonNode config(JsonPath::builtin("config/widgets/lobbyWindow.json")); build(config); } +std::shared_ptr LobbyWidget::getMessageInput() +{ + return widget("messageInput"); +} + LobbyWindow::LobbyWindow(): CWindowObject(BORDERED) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - widget = std::make_shared(); + widget = std::make_shared(this); pos = widget->pos; center(); - connection = std::make_shared(); + connection = std::make_shared(this); connection->start("127.0.0.1", 30303); @@ -59,3 +86,16 @@ void LobbyWindow::tick(uint32_t msPassed) { connection->poll(); } + +void LobbyWindow::doSendChatMessage() +{ + std::string messageText = widget->getMessageInput()->getText(); + + JsonNode toSend; + toSend["type"].String() = "sendChatMessage"; + toSend["messageText"].String() = messageText; + + connection->sendMessage(toSend); + + widget->getMessageInput()->setText(""); +} diff --git a/client/serverLobby/LobbyWindow.h b/client/serverLobby/LobbyWindow.h index d13c1d1a6..0aaf96b6a 100644 --- a/client/serverLobby/LobbyWindow.h +++ b/client/serverLobby/LobbyWindow.h @@ -14,19 +14,29 @@ #include "../../lib/network/NetworkClient.h" +class LobbyWindow; + class LobbyWidget : public InterfaceObjectConfigurable { + LobbyWindow * window; public: - LobbyWidget(); + LobbyWidget(LobbyWindow * window); + + std::shared_ptr getMessageInput(); }; class LobbyClient : public NetworkClient { + LobbyWindow * window; + void onPacketReceived(const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; void onDisconnected() override; + public: - LobbyClient() = default; + explicit LobbyClient(LobbyWindow * window); + + void sendMessage(const JsonNode & data); }; class LobbyWindow : public CWindowObject @@ -38,4 +48,8 @@ class LobbyWindow : public CWindowObject public: LobbyWindow(); + + void doSendChatMessage(); + + void onGameChatMessage(std::string sender, std::string message, std::string when); }; diff --git a/config/widgets/lobbyWindow.json b/config/widgets/lobbyWindow.json index 672cf34dd..42dba15c9 100644 --- a/config/widgets/lobbyWindow.json +++ b/config/widgets/lobbyWindow.json @@ -85,15 +85,25 @@ "position": {"x": 440, "y": 53}, "text" : "Game Chat" }, + { + "type": "textBox", + "name": "gameChat", + "font": "small", + "alignment": "left", + "color": "white", + "text": "[00:00]{Player 1}: Hello\n[00:01]{Player 2}: HI!", + "rect": {"x": 430, "y": 70, "w": 430, "h": 495} + }, { "type": "areaFilled", "rect": {"x": 430, "y": 565, "w": 395, "h": 25} }, { - "type": "labelTitle", - "position": {"x": 440, "y": 568}, - "text" : "Enter Message" + "name" : "messageInput", + "type": "textInput", + "alignment" : "left", + "rect": {"x": 440, "y": 568, "w": 375, "h": 20} }, { @@ -112,7 +122,7 @@ "image": "settingsWindow/button80", "help": "core.help.288", "callback": "closeWindow", - "hotkey": "globalReturn", + "hotkey": "globalCancel", "items": [ { @@ -130,8 +140,8 @@ "position": {"x": 828, "y": 565}, "image": "settingsWindow/button32", "help": "core.help.288", - "callback": "closeWindow", - "hotkey": "globalReturn", + "callback": "sendMessage", + "hotkey": "globalAccept", "items": [ { @@ -149,8 +159,6 @@ "position": {"x": 10, "y": 520}, "image": "settingsWindow/button190", "help": "core.help.288", - "callback": "closeWindow", - "hotkey": "globalReturn", "items": [ { @@ -168,8 +176,6 @@ "position": {"x": 10, "y": 555}, "image": "settingsWindow/button190", "help": "core.help.288", - "callback": "closeWindow", - "hotkey": "globalReturn", "items": [ { diff --git a/lib/network/NetworkClient.h b/lib/network/NetworkClient.h index 5cc87a1ec..60ee493f3 100644 --- a/lib/network/NetworkClient.h +++ b/lib/network/NetworkClient.h @@ -32,12 +32,12 @@ protected: virtual void onConnectionFailed(const std::string & errorMessage) = 0; virtual void onDisconnected() = 0; + void sendPacket(const std::vector & message); public: NetworkClient(); virtual ~NetworkClient() = default; void start(const std::string & host, uint16_t port); - void sendPacket(const std::vector & message); void run(); void poll(); }; diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index 4c0a26471..5cb567f94 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -77,7 +77,7 @@ void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, u message.resize(expectedPacketSize); std::istream istream(&readBuffer); - istream.read(reinterpret_cast(message.data()), messageHeaderSize); + istream.read(reinterpret_cast(message.data()), expectedPacketSize); listener.onPacketReceived(shared_from_this(), message); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index cdbc20a58..3aae7ba45 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -12,6 +12,8 @@ #include "SQLiteConnection.h" +#include "../lib/JsonNode.h" + #include #include @@ -20,6 +22,53 @@ static const int LISTENING_PORT = 30303; //static const std::string SERVER_NAME = GameConstants::VCMI_VERSION + " (server)"; //static const std::string SERVER_UUID = boost::uuids::to_string(boost::uuids::random_generator()()); +void LobbyDatabase::prepareStatements() +{ + static const std::string insertChatMessageText = R"( + INSERT INTO chatMessages(senderName, messageText) VALUES( ?, ?); + )"; + + insertChatMessageStatement = database->prepare(insertChatMessageText); +} + +void LobbyDatabase::createTableChatMessages() +{ + static const std::string statementText = R"( + CREATE TABLE IF NOT EXISTS chatMessages ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + senderName TEXT, + messageText TEXT, + sendTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL + ); + )"; + + auto statement = database->prepare(statementText); + statement->execute(); +} + +void LobbyDatabase::initializeDatabase() +{ + createTableChatMessages(); +} + +LobbyDatabase::LobbyDatabase() +{ + database = SQLiteInstance::open(DATABASE_PATH, true); + + if (!database) + throw std::runtime_error("Failed to open SQLite database!"); + + initializeDatabase(); + prepareStatements(); +} + +void LobbyDatabase::insertChatMessage(const std::string & sender, const std::string & messageText) +{ + insertChatMessageStatement->setBinds(sender, messageText); + insertChatMessageStatement->execute(); + insertChatMessageStatement->reset(); +} + void LobbyServer::onNewConnection(const std::shared_ptr &) { @@ -27,12 +76,19 @@ void LobbyServer::onNewConnection(const std::shared_ptr &) void LobbyServer::onPacketReceived(const std::shared_ptr &, const std::vector & message) { + // FIXME: find better approach + const char * payloadBegin = reinterpret_cast(message.data()); + JsonNode json(payloadBegin, message.size()); + if (json["type"].String() == "sendChatMessage") + { + database->insertChatMessage("Unknown", json["messageText"].String()); + } } LobbyServer::LobbyServer() + : database(new LobbyDatabase()) { - database = SQLiteInstance::open(DATABASE_PATH, true); } int main(int argc, const char * argv[]) diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index 329d831b5..f7114e6c4 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -12,10 +12,25 @@ #include "../lib/network/NetworkServer.h" class SQLiteInstance; +class SQLiteStatement; + +class LobbyDatabase +{ + std::unique_ptr database; + std::unique_ptr insertChatMessageStatement; + + void initializeDatabase(); + void prepareStatements(); + void createTableChatMessages(); +public: + LobbyDatabase(); + + void insertChatMessage(const std::string & sender, const std::string & messageText); +}; class LobbyServer : public NetworkServer { - std::unique_ptr database; + std::unique_ptr database; void onNewConnection(const std::shared_ptr &) override; void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; diff --git a/lobby/SQLiteConnection.cpp b/lobby/SQLiteConnection.cpp index cac16ccb2..16cffb950 100644 --- a/lobby/SQLiteConnection.cpp +++ b/lobby/SQLiteConnection.cpp @@ -16,7 +16,8 @@ static void on_sqlite_error( sqlite3 * connection, [[maybe_unused]] int result ) { if ( result != SQLITE_OK ) { - printf( "sqlite error: %s\n", sqlite3_errmsg( connection ) ); + const char * message = sqlite3_errmsg( connection ); + printf( "sqlite error: %s\n", message ); } assert( result == SQLITE_OK ); From 07fb313765109ee6ebbfc30565e691d6f59b3312 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 12 Nov 2023 16:35:53 +0200 Subject: [PATCH 05/98] Implemented loading of latest messages on joining chat --- client/serverLobby/LobbyWindow.cpp | 47 +++++++++++++++++++++++- client/serverLobby/LobbyWindow.h | 5 ++- lib/network/NetworkServer.cpp | 1 + lobby/LobbyServer.cpp | 57 +++++++++++++++++++++++++++++- lobby/LobbyServer.h | 15 ++++++++ 5 files changed, 122 insertions(+), 3 deletions(-) diff --git a/client/serverLobby/LobbyWindow.cpp b/client/serverLobby/LobbyWindow.cpp index 7f670b58d..858858ab9 100644 --- a/client/serverLobby/LobbyWindow.cpp +++ b/client/serverLobby/LobbyWindow.cpp @@ -14,9 +14,10 @@ #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" #include "../widgets/TextControls.h" - #include "../windows/InfoWindows.h" +#include "../../lib/MetaString.h" + LobbyClient::LobbyClient(LobbyWindow * window) : window(window) { @@ -25,7 +26,33 @@ LobbyClient::LobbyClient(LobbyWindow * window) void LobbyClient::onPacketReceived(const std::vector & message) { + // FIXME: find better approach + const char * payloadBegin = reinterpret_cast(message.data()); + JsonNode json(payloadBegin, message.size()); + if (json["type"].String() == "chatHistory") + { + for (auto const & entry : json["messages"].Vector()) + { + std::string senderName = entry["senderName"].String(); + std::string messageText = entry["messageText"].String(); + int ageSeconds = entry["ageSeconds"].Integer(); + + // FIXME: better/unified way to format date + auto timeNowChrono = std::chrono::system_clock::now(); + timeNowChrono -= std::chrono::seconds(ageSeconds); + + std::time_t timeNowC = std::chrono::system_clock::to_time_t(timeNowChrono); + std::tm timeNowTm = *std::localtime(&timeNowC); + + MetaString dateFormatted; + dateFormatted.appendRawString("%d:%d"); + dateFormatted.replaceNumber(timeNowTm.tm_hour); + dateFormatted.replaceNumber(timeNowTm.tm_min); + + window->onGameChatMessage(senderName, messageText, dateFormatted.toString()); + } + } } void LobbyClient::onConnectionFailed(const std::string & errorMessage) @@ -68,6 +95,11 @@ std::shared_ptr LobbyWidget::getMessageInput() return widget("messageInput"); } +std::shared_ptr LobbyWidget::getGameChat() +{ + return widget("gameChat"); +} + LobbyWindow::LobbyWindow(): CWindowObject(BORDERED) { @@ -99,3 +131,16 @@ void LobbyWindow::doSendChatMessage() widget->getMessageInput()->setText(""); } + +void LobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when) +{ + MetaString chatMessageFormatted; + chatMessageFormatted.appendRawString("[%s] {%s}: %s\n"); + chatMessageFormatted.replaceRawString(when); + chatMessageFormatted.replaceRawString(sender); + chatMessageFormatted.replaceRawString(message); + + chatHistory += chatMessageFormatted.toString(); + + widget->getGameChat()->setText(chatHistory); +} diff --git a/client/serverLobby/LobbyWindow.h b/client/serverLobby/LobbyWindow.h index 0aaf96b6a..7fe54a001 100644 --- a/client/serverLobby/LobbyWindow.h +++ b/client/serverLobby/LobbyWindow.h @@ -23,6 +23,7 @@ public: LobbyWidget(LobbyWindow * window); std::shared_ptr getMessageInput(); + std::shared_ptr getGameChat(); }; class LobbyClient : public NetworkClient @@ -41,6 +42,8 @@ public: class LobbyWindow : public CWindowObject { + std::string chatHistory; + std::shared_ptr widget; std::shared_ptr connection; @@ -51,5 +54,5 @@ public: void doSendChatMessage(); - void onGameChatMessage(std::string sender, std::string message, std::string when); + void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when); }; diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp index 7e201c83f..157a68958 100644 --- a/lib/network/NetworkServer.cpp +++ b/lib/network/NetworkServer.cpp @@ -43,6 +43,7 @@ void NetworkServer::connectionAccepted(std::shared_ptr upcomingCo auto connection = std::make_shared(upcomingConnection, *this); connections.insert(connection); connection->start(); + onNewConnection(connection); startAsyncAccept(); } diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 3aae7ba45..be69c74ce 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -28,7 +28,16 @@ void LobbyDatabase::prepareStatements() INSERT INTO chatMessages(senderName, messageText) VALUES( ?, ?); )"; + static const std::string getRecentMessageHistoryText = R"( + SELECT senderName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',sendTime) AS secondsElapsed + FROM chatMessages + WHERE secondsElapsed < 60*60*24 + ORDER BY sendTime DESC + LIMIT 100 + )"; + insertChatMessageStatement = database->prepare(insertChatMessageText); + getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText); } void LobbyDatabase::createTableChatMessages() @@ -69,9 +78,55 @@ void LobbyDatabase::insertChatMessage(const std::string & sender, const std::str insertChatMessageStatement->reset(); } -void LobbyServer::onNewConnection(const std::shared_ptr &) +std::vector LobbyDatabase::getRecentMessageHistory() { + std::vector result; + while(getRecentMessageHistoryStatement->execute()) + { + LobbyDatabase::ChatMessage message; + getRecentMessageHistoryStatement->getColumns(message.sender, message.messageText, message.messageAgeSeconds); + result.push_back(message); + } + getRecentMessageHistoryStatement->reset(); + + return result; +} + +void LobbyServer::sendMessage(const std::shared_ptr & target, const JsonNode & json) +{ + //FIXME: copy-paste from LobbyClient::sendMessage + std::string payloadString = json.toJson(true); + + // FIXME: find better approach + uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); + uint8_t * payloadEnd = payloadBegin + payloadString.size(); + + std::vector payloadBuffer(payloadBegin, payloadEnd); + + sendPacket(target, payloadBuffer); +} + +void LobbyServer::onNewConnection(const std::shared_ptr & connection) +{ + // FIXME: move to authorization message reply + auto history = database->getRecentMessageHistory(); + + JsonNode json; + json["type"].String() = "chatHistory"; + + for (auto const & message : boost::adaptors::reverse(history)) + { + JsonNode jsonEntry; + + jsonEntry["messageText"].String() = message.messageText; + jsonEntry["senderName"].String() = message.sender; + jsonEntry["ageSeconds"].Integer() = message.messageAgeSeconds; + + json["messages"].Vector().push_back(jsonEntry); + } + + sendMessage(connection, json); } void LobbyServer::onPacketReceived(const std::shared_ptr &, const std::vector & message) diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index f7114e6c4..8e42a6248 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -11,6 +11,10 @@ #include "../lib/network/NetworkServer.h" +VCMI_LIB_NAMESPACE_BEGIN +class JsonNode; +VCMI_LIB_NAMESPACE_END + class SQLiteInstance; class SQLiteStatement; @@ -18,14 +22,23 @@ class LobbyDatabase { std::unique_ptr database; std::unique_ptr insertChatMessageStatement; + std::unique_ptr getRecentMessageHistoryStatement; void initializeDatabase(); void prepareStatements(); void createTableChatMessages(); public: + struct ChatMessage + { + std::string sender; + std::string messageText; + int messageAgeSeconds; + }; + LobbyDatabase(); void insertChatMessage(const std::string & sender, const std::string & messageText); + std::vector getRecentMessageHistory(); }; class LobbyServer : public NetworkServer @@ -34,6 +47,8 @@ class LobbyServer : public NetworkServer void onNewConnection(const std::shared_ptr &) override; void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; + + void sendMessage(const std::shared_ptr & target, const JsonNode & json); public: LobbyServer(); }; From de5227142b80482496cff4374d81401a10d77e20 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 12 Nov 2023 21:23:42 +0200 Subject: [PATCH 06/98] Implemented message receiving / broadcasting --- client/serverLobby/LobbyWindow.cpp | 57 +++++++++++++++++++------- client/serverLobby/LobbyWindow.h | 2 + config/widgets/lobbyWindow.json | 7 ++-- lib/network/NetworkClient.cpp | 2 + lib/network/NetworkClient.h | 1 + lib/network/NetworkConnection.h | 2 +- lib/network/NetworkServer.cpp | 1 + lib/network/NetworkServer.h | 1 + lobby/LobbyServer.cpp | 66 +++++++++++++++++++++--------- lobby/LobbyServer.h | 8 ++++ 10 files changed, 108 insertions(+), 39 deletions(-) diff --git a/client/serverLobby/LobbyWindow.cpp b/client/serverLobby/LobbyWindow.cpp index 858858ab9..14049cf37 100644 --- a/client/serverLobby/LobbyWindow.cpp +++ b/client/serverLobby/LobbyWindow.cpp @@ -17,6 +17,7 @@ #include "../windows/InfoWindows.h" #include "../../lib/MetaString.h" +#include "../../lib/CConfigHandler.h" LobbyClient::LobbyClient(LobbyWindow * window) : window(window) @@ -24,6 +25,23 @@ LobbyClient::LobbyClient(LobbyWindow * window) } +static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0) +{ + // FIXME: better/unified way to format date + auto timeNowChrono = std::chrono::system_clock::now(); + timeNowChrono += std::chrono::seconds(timeOffsetSeconds); + + std::time_t timeNowC = std::chrono::system_clock::to_time_t(timeNowChrono); + std::tm timeNowTm = *std::localtime(&timeNowC); + + MetaString timeFormatted; + timeFormatted.appendRawString("%d:%d"); + timeFormatted.replaceNumber(timeNowTm.tm_hour); + timeFormatted.replaceNumber(timeNowTm.tm_min); + + return timeFormatted.toString(); +} + void LobbyClient::onPacketReceived(const std::vector & message) { // FIXME: find better approach @@ -37,22 +55,27 @@ void LobbyClient::onPacketReceived(const std::vector & message) std::string senderName = entry["senderName"].String(); std::string messageText = entry["messageText"].String(); int ageSeconds = entry["ageSeconds"].Integer(); - - // FIXME: better/unified way to format date - auto timeNowChrono = std::chrono::system_clock::now(); - timeNowChrono -= std::chrono::seconds(ageSeconds); - - std::time_t timeNowC = std::chrono::system_clock::to_time_t(timeNowChrono); - std::tm timeNowTm = *std::localtime(&timeNowC); - - MetaString dateFormatted; - dateFormatted.appendRawString("%d:%d"); - dateFormatted.replaceNumber(timeNowTm.tm_hour); - dateFormatted.replaceNumber(timeNowTm.tm_min); - - window->onGameChatMessage(senderName, messageText, dateFormatted.toString()); + std::string timeFormatted = getCurrentTimeFormatted(-ageSeconds); + window->onGameChatMessage(senderName, messageText, timeFormatted); } } + + if (json["type"].String() == "chatMessage") + { + std::string senderName = json["senderName"].String(); + std::string messageText = json["messageText"].String(); + std::string timeFormatted = getCurrentTimeFormatted(); + window->onGameChatMessage(senderName, messageText, timeFormatted); + } +} + +void LobbyClient::onConnectionEstablished() +{ + JsonNode toSend; + toSend["type"].String() = "authentication"; + toSend["accountName"].String() = settings["general"]["playerName"].String(); + + sendMessage(toSend); } void LobbyClient::onConnectionFailed(const std::string & errorMessage) @@ -90,6 +113,11 @@ LobbyWidget::LobbyWidget(LobbyWindow * window) build(config); } +std::shared_ptr LobbyWidget::getAccountNameLabel() +{ + return widget("accountNameLabel"); +} + std::shared_ptr LobbyWidget::getMessageInput() { return widget("messageInput"); @@ -110,6 +138,7 @@ LobbyWindow::LobbyWindow(): connection = std::make_shared(this); connection->start("127.0.0.1", 30303); + widget->getAccountNameLabel()->setText(settings["general"]["playerName"].String()); addUsedEvents(TIME); } diff --git a/client/serverLobby/LobbyWindow.h b/client/serverLobby/LobbyWindow.h index 7fe54a001..45f108316 100644 --- a/client/serverLobby/LobbyWindow.h +++ b/client/serverLobby/LobbyWindow.h @@ -22,6 +22,7 @@ class LobbyWidget : public InterfaceObjectConfigurable public: LobbyWidget(LobbyWindow * window); + std::shared_ptr getAccountNameLabel(); std::shared_ptr getMessageInput(); std::shared_ptr getGameChat(); }; @@ -32,6 +33,7 @@ class LobbyClient : public NetworkClient void onPacketReceived(const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; + void onConnectionEstablished() override; void onDisconnected() override; public: diff --git a/config/widgets/lobbyWindow.json b/config/widgets/lobbyWindow.json index 42dba15c9..9c98d5e0e 100644 --- a/config/widgets/lobbyWindow.json +++ b/config/widgets/lobbyWindow.json @@ -41,9 +41,9 @@ "rect": {"x": 5, "y": 5, "w": 250, "h": 40} }, { + "name" : "accountNameLabel", "type": "labelTitleMain", - "position": {"x": 15, "y": 10}, - "text" : "Player Name" + "position": {"x": 15, "y": 10} }, { @@ -91,8 +91,7 @@ "font": "small", "alignment": "left", "color": "white", - "text": "[00:00]{Player 1}: Hello\n[00:01]{Player 2}: HI!", - "rect": {"x": 430, "y": 70, "w": 430, "h": 495} + "rect": {"x": 440, "y": 70, "w": 430, "h": 495} }, { diff --git a/lib/network/NetworkClient.cpp b/lib/network/NetworkClient.cpp index d46b0ac2e..e4ce10ab2 100644 --- a/lib/network/NetworkClient.cpp +++ b/lib/network/NetworkClient.cpp @@ -38,6 +38,8 @@ void NetworkClient::onConnected(const boost::system::error_code & ec) connection = std::make_shared(socket, *this); connection->start(); + + onConnectionEstablished(); } void NetworkClient::run() diff --git a/lib/network/NetworkClient.h b/lib/network/NetworkClient.h index 60ee493f3..2f1509313 100644 --- a/lib/network/NetworkClient.h +++ b/lib/network/NetworkClient.h @@ -30,6 +30,7 @@ class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionL protected: virtual void onPacketReceived(const std::vector & message) = 0; virtual void onConnectionFailed(const std::string & errorMessage) = 0; + virtual void onConnectionEstablished() = 0; virtual void onDisconnected() = 0; void sendPacket(const std::vector & message); diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h index 97b1a7f9a..7b2259565 100644 --- a/lib/network/NetworkConnection.h +++ b/lib/network/NetworkConnection.h @@ -16,7 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN class DLL_LINKAGE NetworkConnection :public std::enable_shared_from_this, boost::noncopyable { static const int messageHeaderSize = sizeof(uint32_t); - static const int messageMaxSize = 1024; + static const int messageMaxSize = 65536; // arbitrary size to prevent potential massive allocation if we receive garbage input std::shared_ptr socket; diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp index 157a68958..d24864041 100644 --- a/lib/network/NetworkServer.cpp +++ b/lib/network/NetworkServer.cpp @@ -56,6 +56,7 @@ void NetworkServer::onDisconnected(const std::shared_ptr & co { assert(connections.count(connection)); connections.erase(connection); + onConnectionLost(connection); } VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkServer.h b/lib/network/NetworkServer.h index 54cb7c8cd..716dabe9b 100644 --- a/lib/network/NetworkServer.h +++ b/lib/network/NetworkServer.h @@ -27,6 +27,7 @@ class DLL_LINKAGE NetworkServer : boost::noncopyable, public INetworkConnectionL void onDisconnected(const std::shared_ptr & connection) override; protected: virtual void onNewConnection(const std::shared_ptr &) = 0; + virtual void onConnectionLost(const std::shared_ptr &) = 0; void sendPacket(const std::shared_ptr &, const std::vector & message); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index be69c74ce..0d1c9c183 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -109,27 +109,14 @@ void LobbyServer::sendMessage(const std::shared_ptr & target, void LobbyServer::onNewConnection(const std::shared_ptr & connection) { - // FIXME: move to authorization message reply - auto history = database->getRecentMessageHistory(); - - JsonNode json; - json["type"].String() = "chatHistory"; - - for (auto const & message : boost::adaptors::reverse(history)) - { - JsonNode jsonEntry; - - jsonEntry["messageText"].String() = message.messageText; - jsonEntry["senderName"].String() = message.sender; - jsonEntry["ageSeconds"].Integer() = message.messageAgeSeconds; - - json["messages"].Vector().push_back(jsonEntry); - } - - sendMessage(connection, json); } -void LobbyServer::onPacketReceived(const std::shared_ptr &, const std::vector & message) +void LobbyServer::onConnectionLost(const std::shared_ptr & connection) +{ + activeAccounts.erase(connection); +} + +void LobbyServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) { // FIXME: find better approach const char * payloadBegin = reinterpret_cast(message.data()); @@ -137,7 +124,46 @@ void LobbyServer::onPacketReceived(const std::shared_ptr &, c if (json["type"].String() == "sendChatMessage") { - database->insertChatMessage("Unknown", json["messageText"].String()); + if (activeAccounts.count(connection) == 0) + return; // unauthenticated + + std::string senderName = activeAccounts[connection].accountName; + std::string messageText = json["messageText"].String(); + + database->insertChatMessage(senderName, messageText); + + JsonNode reply; + reply["type"].String() = "chatMessage"; + reply["messageText"].String() = messageText; + reply["senderName"].String() = senderName; + + for (auto const & connection : activeAccounts) + sendMessage(connection.first, reply); + } + + if (json["type"].String() == "authentication") + { + std::string accountName = json["accountName"].String(); + + activeAccounts[connection].accountName = accountName; + + auto history = database->getRecentMessageHistory(); + + JsonNode json; + json["type"].String() = "chatHistory"; + + for (auto const & message : boost::adaptors::reverse(history)) + { + JsonNode jsonEntry; + + jsonEntry["messageText"].String() = message.messageText; + jsonEntry["senderName"].String() = message.sender; + jsonEntry["ageSeconds"].Integer() = message.messageAgeSeconds; + + json["messages"].Vector().push_back(jsonEntry); + } + + sendMessage(connection, json); } } diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index 8e42a6248..ceaad47bc 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -43,9 +43,17 @@ public: class LobbyServer : public NetworkServer { + struct AccountState + { + std::string accountName; + }; + + std::map, AccountState> activeAccounts; + std::unique_ptr database; void onNewConnection(const std::shared_ptr &) override; + void onConnectionLost(const std::shared_ptr &) override; void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; void sendMessage(const std::shared_ptr & target, const JsonNode & json); From 0a1153e1c6645974b88e689d282934ea9029c05e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 18 Nov 2023 16:34:18 +0200 Subject: [PATCH 07/98] Switch client-server communication to new API --- client/CServerHandler.cpp | 188 ++++---- client/CServerHandler.h | 20 +- client/Client.cpp | 3 +- client/NetPacksClient.cpp | 2 +- client/NetPacksLobbyClient.cpp | 2 - client/lobby/CSelectionBase.cpp | 2 +- client/lobby/SelectionTab.cpp | 1 - client/mainmenu/CMainMenu.cpp | 55 ++- client/mainmenu/CMainMenu.h | 3 +- client/serverLobby/LobbyWindow.cpp | 25 +- client/serverLobby/LobbyWindow.h | 9 +- cmake_modules/VCMI_lib.cmake | 1 + lib/IGameCallback.cpp | 2 - lib/StartInfo.cpp | 7 +- lib/StartInfo.h | 4 +- lib/network/NetworkClient.cpp | 29 +- lib/network/NetworkClient.h | 19 +- lib/network/NetworkConnection.h | 1 + lib/network/NetworkDefines.h | 12 - lib/network/NetworkListener.h | 50 +++ lib/network/NetworkServer.cpp | 20 +- lib/network/NetworkServer.h | 13 +- lib/serializer/CSerializer.h | 2 +- lib/serializer/Connection.cpp | 49 ++- lib/serializer/Connection.h | 149 +++---- lobby/LobbyServer.cpp | 15 +- lobby/LobbyServer.h | 8 +- server/CGameHandler.cpp | 27 +- server/CGameHandler.h | 3 +- server/CVCMIServer.cpp | 435 +++++++------------ server/CVCMIServer.h | 71 +-- server/NetPacksLobbyServer.cpp | 80 +--- server/NetPacksServer.cpp | 2 +- server/processors/PlayerMessageProcessor.cpp | 8 +- 34 files changed, 623 insertions(+), 694 deletions(-) create mode 100644 lib/network/NetworkListener.h diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index a26d4aac7..a725d2ed5 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -45,17 +45,17 @@ #include "../lib/mapping/CMapInfo.h" #include "../lib/mapObjects/MiscObjects.h" #include "../lib/modding/ModIncompatibility.h" +#include "../lib/network/NetworkClient.h" #include "../lib/rmg/CMapGenOptions.h" +#include "../lib/serializer/Connection.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/registerTypes/RegisterTypesLobbyPacks.h" -#include "../lib/serializer/Connection.h" #include "../lib/serializer/CMemorySerializer.h" #include "../lib/UnlockGuard.h" #include #include #include -#include #include "../lib/serializer/Cast.h" #include "LobbyClientNetPackVisitors.h" @@ -131,8 +131,16 @@ public: static const std::string NAME_AFFIX = "client"; static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name +CServerHandler::~CServerHandler() = default; + CServerHandler::CServerHandler() - : state(EClientState::NONE), mx(std::make_shared()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false) + : state(EClientState::NONE) + , mx(std::make_shared()) + , networkClient(std::make_unique(*this)) + , client(nullptr) + , loadMode(0) + , campaignStateToSend(nullptr) + , campaignServerRestartLock(false) { uuid = boost::uuids::to_string(boost::uuids::random_generator()()); //read from file to restore last session @@ -161,7 +169,7 @@ void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std:: myNames.push_back(settings["general"]["playerName"].String()); } -void CServerHandler::startLocalServerAndConnect() +void CServerHandler::startLocalServerAndConnect(const std::function & onConnected) { if(threadRunLocalServer) threadRunLocalServer->join(); @@ -169,17 +177,13 @@ void CServerHandler::startLocalServerAndConnect() th->update(); auto errorMsg = CGI->generaltexth->translate("vcmi.server.errors.existingProcess"); - try + + if (!checkNetworkPortIsFree(localhostAddress, getDefaultPort())) { - CConnection testConnection(localhostAddress, getDefaultPort(), NAME, uuid); logNetwork->error("Port is busy, check if another instance of vcmiserver is working"); CInfoWindow::showInfoDialog(errorMsg, {}); return; } - catch(std::runtime_error & error) - { - //no connection means that port is not busy and we can start local server - } #if defined(SINGLE_PROCESS_APP) boost::condition_variable cond; @@ -243,38 +247,15 @@ void CServerHandler::startLocalServerAndConnect() th->update(); //put breakpoint here to attach to server before it does something stupid - justConnectToServer(localhostAddress, 0); + justConnectToServer(localhostAddress, 0, onConnected); logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff()); } -void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port) +void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port, const std::function & onConnected) { + logNetwork->info("Establishing connection..."); state = EClientState::CONNECTING; - while(!c && state != EClientState::CONNECTION_CANCELLED) - { - try - { - logNetwork->info("Establishing connection..."); - c = std::make_shared( - addr.size() ? addr : getHostAddress(), - port ? port : getHostPort(), - NAME, uuid); - } - catch(std::runtime_error & error) - { - logNetwork->warn("\nCannot establish connection. %s Retrying in 1 second", error.what()); - boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); - } - } - - if(state == EClientState::CONNECTION_CANCELLED) - { - logNetwork->info("Connection aborted by player!"); - return; - } - - c->handler = std::make_shared(&CServerHandler::threadHandleConnection, this); if(!addr.empty() && addr != getHostAddress()) { @@ -286,13 +267,39 @@ void CServerHandler::justConnectToServer(const std::string & addr, const ui16 po Settings serverPort = settings.write["server"]["port"]; serverPort->Integer() = port; } + + networkClient->start(addr.size() ? addr : getHostAddress(), port ? port : getHostPort()); +} + +void CServerHandler::onConnectionFailed(const std::string & errorMessage) +{ + if(state == EClientState::CONNECTION_CANCELLED) + { + logNetwork->info("Connection aborted by player!"); + return; + } + + logNetwork->warn("\nCannot establish connection. %s Retrying in 1 second", errorMessage); + + //FIXME: replace with asio timer + boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); + + //FIXME: pass parameters from initial attempt + networkClient->start(getHostAddress(), getHostPort()); +} + +void CServerHandler::onConnectionEstablished() +{ + c->enterLobbyConnectionMode(); + sendClientConnecting(); + + //FIXME: call functor provided in CServerHandler::justConnectToServer + assert(0); + //onConnected(); } void CServerHandler::applyPacksOnLobbyScreen() { - if(!c || !c->handler) - return; - boost::unique_lock lock(*mx); while(!packsForLobbyScreen.empty()) { @@ -306,16 +313,6 @@ void CServerHandler::applyPacksOnLobbyScreen() } } -void CServerHandler::stopServerConnection() -{ - if(c->handler) - { - while(!c->handler->timed_join(boost::chrono::milliseconds(50))) - applyPacksOnLobbyScreen(); - c->handler->join(); - } -} - std::set CServerHandler::getHumanColors() { return clientHumanColors(c->connectionID); @@ -421,7 +418,6 @@ void CServerHandler::sendClientDisconnecting() { // Network thread might be applying network pack at this moment auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); - c->close(); c.reset(); } } @@ -814,7 +810,7 @@ void CServerHandler::restoreLastSession() myNames.push_back(name.String()); resetStateForLobby(StartInfo::LOAD_GAME, &myNames); screenType = ESelectionScreen::loadGame; - justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer()); + justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer(), {}); }; auto cleanUpSession = []() @@ -844,9 +840,9 @@ void CServerHandler::debugStartTest(std::string filename, bool save) screenType = ESelectionScreen::newGame; } if(settings["session"]["donotstartserver"].Bool()) - justConnectToServer(localhostAddress, 3030); + justConnectToServer(localhostAddress, 3030, {}); else - startLocalServerAndConnect(); + startLocalServerAndConnect({}); boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); @@ -902,65 +898,49 @@ public: } }; -void CServerHandler::threadHandleConnection() +void CServerHandler::onPacketReceived(const std::vector & message) { - setThreadName("handleConnection"); - c->enterLobbyConnectionMode(); - - try + CPack * pack = c->retrievePack(message); + if(state == EClientState::DISCONNECTING) { - sendClientConnecting(); - while(c && c->connected) - { - while(state == EClientState::STARTING) - boost::this_thread::sleep_for(boost::chrono::milliseconds(10)); - - CPack * pack = c->retrievePack(); - if(state == EClientState::DISCONNECTING) - { - // FIXME: server shouldn't really send netpacks after it's tells client to disconnect - // Though currently they'll be delivered and might cause crash. - vstd::clear_pointer(pack); - } - else - { - ServerHandlerCPackVisitor visitor(*this); - pack->visit(visitor); - } - } + // FIXME: server shouldn't really send netpacks after it's tells client to disconnect + // Though currently they'll be delivered and might cause crash. + vstd::clear_pointer(pack); } - //catch only asio exceptions - catch(const boost::system::system_error & e) + else { - if(state == EClientState::DISCONNECTING) + ServerHandlerCPackVisitor visitor(*this); + pack->visit(visitor); + } +} + +void CServerHandler::onDisconnected() +{ + if(state == EClientState::DISCONNECTING) + { + logNetwork->info("Successfully closed connection to server, ending listening thread!"); + } + else + { + logNetwork->error("Lost connection to server, ending listening thread! Connection has been closed"); + + if(client) { - logNetwork->info("Successfully closed connection to server, ending listening thread!"); + state = EClientState::DISCONNECTING; + + GH.dispatchMainThread([]() + { + CSH->endGameplay(); + GH.defActionsDef = 63; + CMM->menu->switchToTab("main"); + }); } else { - if (e.code() == boost::asio::error::eof) - logNetwork->error("Lost connection to server, ending listening thread! Connection has been closed"); - else - logNetwork->error("Lost connection to server, ending listening thread! Reason: %s", e.what()); - - if(client) - { - state = EClientState::DISCONNECTING; - - GH.dispatchMainThread([]() - { - CSH->endGameplay(); - GH.defActionsDef = 63; - CMM->menu->switchToTab("main"); - }); - } - else - { - auto lcd = new LobbyClientDisconnected(); - lcd->clientId = c->connectionID; - boost::unique_lock lock(*mx); - packsForLobbyScreen.push_back(lcd); - } + auto lcd = new LobbyClientDisconnected(); + lcd->clientId = c->connectionID; + boost::unique_lock lock(*mx); + packsForLobbyScreen.push_back(lcd); } } } diff --git a/client/CServerHandler.h b/client/CServerHandler.h index e4a5d02ca..5192638e9 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -11,6 +11,7 @@ #include "../lib/CStopWatch.h" +#include "../lib/network/NetworkListener.h" #include "../lib/StartInfo.h" #include "../lib/CondSh.h" @@ -80,8 +81,10 @@ public: }; /// structure to handle running server and connecting to it -class CServerHandler : public IServerAPI, public LobbyInfo +class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClientListener, boost::noncopyable { + std::unique_ptr networkClient; + friend class ApplyOnLobbyHandlerNetPackVisitor; std::shared_ptr> applier; @@ -95,12 +98,18 @@ class CServerHandler : public IServerAPI, public LobbyInfo std::shared_ptr highScoreCalc; - void threadHandleConnection(); void threadRunServer(); void onServerFinished(); void sendLobbyPack(const CPackForLobby & pack) const override; + void onPacketReceived(const std::vector & message) override; + void onConnectionFailed(const std::string & errorMessage) override; + void onConnectionEstablished() override; + void onDisconnected() override; + public: + std::shared_ptr c; + std::atomic state; //////////////////// // FIXME: Bunch of crutches to glue it all together @@ -115,7 +124,6 @@ public: std::unique_ptr th; std::shared_ptr threadRunLocalServer; - std::shared_ptr c; CClient * client; CondSh campaignServerRestartLock; @@ -123,15 +131,15 @@ public: static const std::string localhostAddress; CServerHandler(); + ~CServerHandler(); std::string getHostAddress() const; ui16 getHostPort() const; void resetStateForLobby(const StartInfo::EMode mode, const std::vector * names = nullptr); - void startLocalServerAndConnect(); - void justConnectToServer(const std::string & addr, const ui16 port); + void startLocalServerAndConnect(const std::function & onConnected); + void justConnectToServer(const std::string & addr, const ui16 port, const std::function & onConnected); void applyPacksOnLobbyScreen(); - void stopServerConnection(); // Helpers for lobby state access std::set getHumanColors(); diff --git a/client/Client.cpp b/client/Client.cpp index 0a6dd7109..71230ab23 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -30,11 +30,12 @@ #include "../lib/UnlockGuard.h" #include "../lib/battle/BattleInfo.h" #include "../lib/serializer/BinaryDeserializer.h" +#include "../lib/serializer/BinarySerializer.h" +#include "../lib/serializer/Connection.h" #include "../lib/mapping/CMapService.h" #include "../lib/pathfinder/CGPathNode.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/registerTypes/RegisterTypesClientPacks.h" -#include "../lib/serializer/Connection.h" #include #include diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index a6d0bff9a..aff9f3241 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -27,8 +27,8 @@ #include "../CCallback.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/FileInfo.h" -#include "../lib/serializer/Connection.h" #include "../lib/serializer/BinarySerializer.h" +#include "../lib/serializer/Connection.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/VCMI_Lib.h" diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index aee45c228..46173ca20 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -54,8 +54,6 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientDisconnected(LobbyClient result = false; return; } - - handler.stopServerConnection(); } void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) diff --git a/client/lobby/CSelectionBase.cpp b/client/lobby/CSelectionBase.cpp index 2f602e023..6ead7387c 100644 --- a/client/lobby/CSelectionBase.cpp +++ b/client/lobby/CSelectionBase.cpp @@ -47,7 +47,7 @@ #include "../../lib/filesystem/Filesystem.h" #include "../../lib/mapping/CMapInfo.h" #include "../../lib/mapping/CMapHeader.h" -#include "../../lib/serializer/Connection.h" +#include "../../lib/CRandomGenerator.h" ISelectionScreenInfo::ISelectionScreenInfo(ESelectionScreen ScreenType) : screenType(ScreenType) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index a44c90e5a..0e157458c 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -43,7 +43,6 @@ #include "../../lib/mapping/CMapHeader.h" #include "../../lib/mapping/MapFormat.h" #include "../../lib/TerrainHandler.h" -#include "../../lib/serializer/Connection.h" bool mapSorter::operator()(const std::shared_ptr aaa, const std::shared_ptr bbb) { diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 058d2cd8e..d4aa5705a 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -45,7 +45,6 @@ #include "../../lib/CGeneralTextHandler.h" #include "../../lib/JsonNode.h" #include "../../lib/campaign/CampaignHandler.h" -#include "../../lib/serializer/Connection.h" #include "../../lib/serializer/CTypeList.h" #include "../../lib/filesystem/Filesystem.h" #include "../../lib/filesystem/CCompressedStream.h" @@ -559,7 +558,7 @@ CSimpleJoinScreen::CSimpleJoinScreen(bool host) { textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverConnecting")); buttonOk->block(true); - startConnectThread(); + startConnection(); } else { @@ -582,7 +581,7 @@ void CSimpleJoinScreen::connectToServer() buttonOk->block(true); GH.stopTextInput(); - startConnectThread(inputAddress->getText(), boost::lexical_cast(inputPort->getText())); + startConnection(inputAddress->getText(), boost::lexical_cast(inputPort->getText())); } void CSimpleJoinScreen::leaveScreen() @@ -603,7 +602,7 @@ void CSimpleJoinScreen::onChange(const std::string & newText) buttonOk->block(inputAddress->getText().empty() || inputPort->getText().empty()); } -void CSimpleJoinScreen::startConnectThread(const std::string & addr, ui16 port) +void CSimpleJoinScreen::startConnection(const std::string & addr, ui16 port) { #if defined(SINGLE_PROCESS_APP) && defined(VCMI_ANDROID) // in single process build server must use same JNIEnv as client @@ -611,35 +610,31 @@ void CSimpleJoinScreen::startConnectThread(const std::string & addr, ui16 port) // https://github.com/libsdl-org/SDL/blob/main/docs/README-android.md#threads-and-the-java-vm CVCMIServer::reuseClientJNIEnv(SDL_AndroidGetJNIEnv()); #endif - boost::thread connector(&CSimpleJoinScreen::connectThread, this, addr, port); - connector.detach(); -} + auto const & onConnected = [this]() + { + // async call to prevent thread race + GH.dispatchMainThread([this](){ + if(CSH->state == EClientState::CONNECTION_FAILED) + { + CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {}); -void CSimpleJoinScreen::connectThread(const std::string & addr, ui16 port) -{ - setThreadName("connectThread"); - if(!addr.length()) - CSH->startLocalServerAndConnect(); + textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverAddressEnter")); + GH.startTextInput(inputAddress->pos); + buttonOk->block(false); + } + + if(GH.windows().isTopWindow(this)) + { + close(); + } + }); + }; + + if(addr.empty()) + CSH->startLocalServerAndConnect(onConnected); else - CSH->justConnectToServer(addr, port); - - // async call to prevent thread race - GH.dispatchMainThread([this](){ - if(CSH->state == EClientState::CONNECTION_FAILED) - { - CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {}); - - textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverAddressEnter")); - GH.startTextInput(inputAddress->pos); - buttonOk->block(false); - } - - if(GH.windows().isTopWindow(this)) - { - close(); - } - }); + CSH->justConnectToServer(addr, port, onConnected); } CLoadingScreen::CLoadingScreen() diff --git a/client/mainmenu/CMainMenu.h b/client/mainmenu/CMainMenu.h index ed2cdf585..5c806961f 100644 --- a/client/mainmenu/CMainMenu.h +++ b/client/mainmenu/CMainMenu.h @@ -177,8 +177,7 @@ class CSimpleJoinScreen : public WindowBase void connectToServer(); void leaveScreen(); void onChange(const std::string & newText); - void startConnectThread(const std::string & addr = {}, ui16 port = 0); - void connectThread(const std::string & addr, ui16 port); + void startConnection(const std::string & addr = {}, ui16 port = 0); public: CSimpleJoinScreen(bool host = true); diff --git a/client/serverLobby/LobbyWindow.cpp b/client/serverLobby/LobbyWindow.cpp index 14049cf37..193c56f61 100644 --- a/client/serverLobby/LobbyWindow.cpp +++ b/client/serverLobby/LobbyWindow.cpp @@ -18,12 +18,12 @@ #include "../../lib/MetaString.h" #include "../../lib/CConfigHandler.h" +#include "../../lib/network/NetworkClient.h" LobbyClient::LobbyClient(LobbyWindow * window) - : window(window) -{ - -} + : networkClient(std::make_unique(*this)) + , window(window) +{} static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0) { @@ -100,7 +100,22 @@ void LobbyClient::sendMessage(const JsonNode & data) std::vector payloadBuffer(payloadBegin, payloadEnd); - sendPacket(payloadBuffer); + networkClient->sendPacket(payloadBuffer); +} + +void LobbyClient::start(const std::string & host, uint16_t port) +{ + networkClient->start(host, port); +} + +void LobbyClient::run() +{ + networkClient->run(); +} + +void LobbyClient::poll() +{ + networkClient->poll(); } LobbyWidget::LobbyWidget(LobbyWindow * window) diff --git a/client/serverLobby/LobbyWindow.h b/client/serverLobby/LobbyWindow.h index 45f108316..4121580e0 100644 --- a/client/serverLobby/LobbyWindow.h +++ b/client/serverLobby/LobbyWindow.h @@ -12,7 +12,7 @@ #include "../gui/InterfaceObjectConfigurable.h" #include "../windows/CWindowObject.h" -#include "../../lib/network/NetworkClient.h" +#include "../../lib/network/NetworkListener.h" class LobbyWindow; @@ -27,8 +27,9 @@ public: std::shared_ptr getGameChat(); }; -class LobbyClient : public NetworkClient +class LobbyClient : public INetworkClientListener { + std::unique_ptr networkClient; LobbyWindow * window; void onPacketReceived(const std::vector & message) override; @@ -40,6 +41,10 @@ public: explicit LobbyClient(LobbyWindow * window); void sendMessage(const JsonNode & data); + void start(const std::string & host, uint16_t port); + void run(); + void poll(); + }; class LobbyWindow : public CWindowObject diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index a9a9f8c74..6ad35ac43 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -478,6 +478,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/network/NetworkClient.h ${MAIN_LIB_DIR}/network/NetworkConnection.h ${MAIN_LIB_DIR}/network/NetworkDefines.h + ${MAIN_LIB_DIR}/network/NetworkListener.h ${MAIN_LIB_DIR}/network/NetworkServer.h ${MAIN_LIB_DIR}/networkPacks/ArtifactLocation.h diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index f40aebcd8..06b64daf7 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -47,8 +47,6 @@ #include "RiverHandler.h" #include "TerrainHandler.h" -#include "serializer/Connection.h" - VCMI_LIB_NAMESPACE_BEGIN void CPrivilegedInfoCallback::getFreeTiles(std::vector & tiles) const diff --git a/lib/StartInfo.cpp b/lib/StartInfo.cpp index cf1a70112..d08b1221c 100644 --- a/lib/StartInfo.cpp +++ b/lib/StartInfo.cpp @@ -123,7 +123,12 @@ bool LobbyInfo::isClientHost(int clientId) const return clientId == hostClientId; } -std::set LobbyInfo::getAllClientPlayers(int clientId) +bool LobbyInfo::isPlayerHost(const PlayerColor & color) const +{ + return vstd::contains(getAllClientPlayers(hostClientId), color); +} + +std::set LobbyInfo::getAllClientPlayers(int clientId) const { std::set players; for(auto & elem : si->playerInfos) diff --git a/lib/StartInfo.h b/lib/StartInfo.h index eed03eb21..8e0684ba2 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -197,7 +197,6 @@ struct DLL_LINKAGE LobbyState struct DLL_LINKAGE LobbyInfo : public LobbyState { - boost::mutex stateMutex; std::string uuid; LobbyInfo() {} @@ -205,7 +204,8 @@ struct DLL_LINKAGE LobbyInfo : public LobbyState void verifyStateBeforeStart(bool ignoreNoHuman = false) const; bool isClientHost(int clientId) const; - std::set getAllClientPlayers(int clientId); + bool isPlayerHost(const PlayerColor & color) const; + std::set getAllClientPlayers(int clientId) const; std::vector getConnectedPlayerIdsForClient(int clientId) const; // Helpers for lobby state access diff --git a/lib/network/NetworkClient.cpp b/lib/network/NetworkClient.cpp index e4ce10ab2..dbcbd9315 100644 --- a/lib/network/NetworkClient.cpp +++ b/lib/network/NetworkClient.cpp @@ -13,10 +13,27 @@ VCMI_LIB_NAMESPACE_BEGIN -NetworkClient::NetworkClient() +DLL_LINKAGE bool checkNetworkPortIsFree(const std::string & host, uint16_t port) +{ + boost::asio::io_service io; + NetworkAcceptor acceptor(io); + + boost::system::error_code ec; + acceptor.open(boost::asio::ip::tcp::v4(), ec); + if (ec) + return false; + + acceptor.bind(boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port), ec); + if (ec) + return false; + + return true; +} + +NetworkClient::NetworkClient(INetworkClientListener & listener) : io(new NetworkService) , socket(new NetworkSocket(*io)) -// , timer(new NetworkTimer(*io)) + , listener(listener) { } @@ -32,14 +49,14 @@ void NetworkClient::onConnected(const boost::system::error_code & ec) { if (ec) { - onConnectionFailed(ec.message()); + listener.onConnectionFailed(ec.message()); return; } connection = std::make_shared(socket, *this); connection->start(); - onConnectionEstablished(); + listener.onConnectionEstablished(); } void NetworkClient::run() @@ -59,12 +76,12 @@ void NetworkClient::sendPacket(const std::vector & message) void NetworkClient::onDisconnected(const std::shared_ptr & connection) { - onDisconnected(); + listener.onDisconnected(); } void NetworkClient::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) { - onPacketReceived(message); + listener.onPacketReceived(message); } diff --git a/lib/network/NetworkClient.h b/lib/network/NetworkClient.h index 2f1509313..541d34d23 100644 --- a/lib/network/NetworkClient.h +++ b/lib/network/NetworkClient.h @@ -10,9 +10,14 @@ #pragma once #include "NetworkDefines.h" +#include "NetworkListener.h" VCMI_LIB_NAMESPACE_BEGIN +/// Function that attempts to open specified port on local system to determine whether port is in use +/// Returns: true if port is free and can be used to receive connections +DLL_LINKAGE bool checkNetworkPortIsFree(const std::string & host, uint16_t port); + class NetworkConnection; class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionListener @@ -20,23 +25,19 @@ class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionL std::shared_ptr io; std::shared_ptr socket; std::shared_ptr connection; - std::shared_ptr timer; + + INetworkClientListener & listener; void onConnected(const boost::system::error_code & ec); void onDisconnected(const std::shared_ptr & connection) override; void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; -protected: - virtual void onPacketReceived(const std::vector & message) = 0; - virtual void onConnectionFailed(const std::string & errorMessage) = 0; - virtual void onConnectionEstablished() = 0; - virtual void onDisconnected() = 0; +public: + NetworkClient(INetworkClientListener & listener); + virtual ~NetworkClient() = default; void sendPacket(const std::vector & message); -public: - NetworkClient(); - virtual ~NetworkClient() = default; void start(const std::string & host, uint16_t port); void run(); diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h index 7b2259565..cb72903bd 100644 --- a/lib/network/NetworkConnection.h +++ b/lib/network/NetworkConnection.h @@ -10,6 +10,7 @@ #pragma once #include "NetworkDefines.h" +#include "NetworkListener.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/network/NetworkDefines.h b/lib/network/NetworkDefines.h index 8b05370bb..bfd24eced 100644 --- a/lib/network/NetworkDefines.h +++ b/lib/network/NetworkDefines.h @@ -17,17 +17,5 @@ using NetworkService = boost::asio::io_service; using NetworkSocket = boost::asio::basic_stream_socket; using NetworkAcceptor = boost::asio::basic_socket_acceptor; using NetworkBuffer = boost::asio::streambuf; -using NetworkTimer = boost::asio::steady_timer; - -class NetworkConnection; - -class DLL_LINKAGE INetworkConnectionListener -{ - friend class NetworkConnection; - -protected: - virtual void onDisconnected(const std::shared_ptr & connection) = 0; - virtual void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) = 0; -}; VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkListener.h b/lib/network/NetworkListener.h new file mode 100644 index 000000000..7f3200ef5 --- /dev/null +++ b/lib/network/NetworkListener.h @@ -0,0 +1,50 @@ +/* + * NetworkListener.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + +class NetworkConnection; +class NetworkServer; +class NetworkClient; + +class DLL_LINKAGE INetworkConnectionListener +{ + friend class NetworkConnection; +protected: + virtual void onDisconnected(const std::shared_ptr & connection) = 0; + virtual void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) = 0; + + ~INetworkConnectionListener() = default; +}; + +class DLL_LINKAGE INetworkServerListener : public INetworkConnectionListener +{ + friend class NetworkServer; +protected: + virtual void onNewConnection(const std::shared_ptr &) = 0; + + ~INetworkServerListener() = default; +}; + +class DLL_LINKAGE INetworkClientListener +{ + friend class NetworkClient; +protected: + virtual void onPacketReceived(const std::vector & message) = 0; + virtual void onConnectionFailed(const std::string & errorMessage) = 0; + virtual void onConnectionEstablished() = 0; + virtual void onDisconnected() = 0; + + ~INetworkClientListener() = default; +}; + + +VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp index d24864041..3c95a284d 100644 --- a/lib/network/NetworkServer.cpp +++ b/lib/network/NetworkServer.cpp @@ -13,6 +13,12 @@ VCMI_LIB_NAMESPACE_BEGIN +NetworkServer::NetworkServer(INetworkServerListener & listener) + :listener(listener) +{ + +} + void NetworkServer::start(uint16_t port) { io = std::make_shared(); @@ -32,6 +38,11 @@ void NetworkServer::run() io->run(); } +void NetworkServer::run(std::chrono::milliseconds duration) +{ + io->run_for(duration); +} + void NetworkServer::connectionAccepted(std::shared_ptr upcomingConnection, const boost::system::error_code & ec) { if(ec) @@ -43,7 +54,7 @@ void NetworkServer::connectionAccepted(std::shared_ptr upcomingCo auto connection = std::make_shared(upcomingConnection, *this); connections.insert(connection); connection->start(); - onNewConnection(connection); + listener.onNewConnection(connection); startAsyncAccept(); } @@ -56,7 +67,12 @@ void NetworkServer::onDisconnected(const std::shared_ptr & co { assert(connections.count(connection)); connections.erase(connection); - onConnectionLost(connection); + listener.onDisconnected(connection); +} + +void NetworkServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) +{ + listener.onPacketReceived(connection, message); } VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkServer.h b/lib/network/NetworkServer.h index 716dabe9b..bf3a67c52 100644 --- a/lib/network/NetworkServer.h +++ b/lib/network/NetworkServer.h @@ -10,6 +10,7 @@ #pragma once #include "NetworkDefines.h" +#include "NetworkListener.h" VCMI_LIB_NAMESPACE_BEGIN @@ -21,20 +22,20 @@ class DLL_LINKAGE NetworkServer : boost::noncopyable, public INetworkConnectionL std::shared_ptr acceptor; std::set> connections; + INetworkServerListener & listener; + void connectionAccepted(std::shared_ptr, const boost::system::error_code & ec); void startAsyncAccept(); void onDisconnected(const std::shared_ptr & connection) override; -protected: - virtual void onNewConnection(const std::shared_ptr &) = 0; - virtual void onConnectionLost(const std::shared_ptr &) = 0; + void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; +public: + explicit NetworkServer(INetworkServerListener & listener); void sendPacket(const std::shared_ptr &, const std::vector & message); -public: - virtual ~NetworkServer() = default; - void start(uint16_t port); + void run(std::chrono::milliseconds duration); void run(); }; diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index 58914184c..be5a69cdb 100644 --- a/lib/serializer/CSerializer.h +++ b/lib/serializer/CSerializer.h @@ -51,7 +51,7 @@ struct VectorizedObjectInfo }; /// Base class for serializers capable of reading or writing data -class DLL_LINKAGE CSerializer +class DLL_LINKAGE CSerializer : boost::noncopyable { template, bool> = true> static int32_t idToNumber(const Numeric &t) diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index ed0625296..8d7e8be2e 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -10,9 +10,52 @@ #include "StdInc.h" #include "Connection.h" -#include "../networkPacks/NetPacksBase.h" +#include "BinaryDeserializer.h" +#include "BinarySerializer.h" -#include +//#include "../networkPacks/NetPacksBase.h" + +CConnection::CConnection(std::weak_ptr networkConnection) +{ + +} + +void CConnection::sendPack(const CPack * pack) +{ + +} + +CPack * CConnection::retrievePack(const std::vector & data) +{ + return nullptr; +} + +void CConnection::disableStackSendingByID() +{ + +} + +void CConnection::enterLobbyConnectionMode() +{ + +} + +void CConnection::enterGameplayConnectionMode(CGameState * gs) +{ + +} + +int CConnection::write(const void * data, unsigned size) +{ + return 0; +} + +int CConnection::read(void * data, unsigned size) +{ + return 0; +} + +#if 0 VCMI_LIB_NAMESPACE_BEGIN @@ -362,3 +405,5 @@ std::string CConnection::toString() const } VCMI_LIB_NAMESPACE_END + +#endif diff --git a/lib/serializer/Connection.h b/lib/serializer/Connection.h index 518223246..5dc43b8f6 100644 --- a/lib/serializer/Connection.h +++ b/lib/serializer/Connection.h @@ -9,123 +9,84 @@ */ #pragma once -#include "BinaryDeserializer.h" -#include "BinarySerializer.h" - -#if BOOST_VERSION >= 107000 // Boost version >= 1.70 -#include -using TSocket = boost::asio::basic_stream_socket; -using TAcceptor = boost::asio::basic_socket_acceptor; -#else -namespace boost -{ - namespace asio - { - namespace ip - { - class tcp; - } - -#if BOOST_VERSION >= 106600 // Boost version >= 1.66 - class io_context; - typedef io_context io_service; -#else - class io_service; -#endif - - template class stream_socket_service; - template - class basic_stream_socket; - - template class socket_acceptor_service; - template - class basic_socket_acceptor; - } - class mutex; -} - -typedef boost::asio::basic_stream_socket < boost::asio::ip::tcp , boost::asio::stream_socket_service > TSocket; -typedef boost::asio::basic_socket_acceptor > TAcceptor; -#endif - +#include "CSerializer.h" VCMI_LIB_NAMESPACE_BEGIN +class BinaryDeserializer; +class BinarySerializer; struct CPack; struct ConnectionBuffers; +class NetworkConnection; /// Main class for network communication /// Allows establishing connection and bidirectional read-write -class DLL_LINKAGE CConnection - : public IBinaryReader, public IBinaryWriter, public std::enable_shared_from_this +class DLL_LINKAGE CConnection : public IBinaryReader, public IBinaryWriter, public std::enable_shared_from_this { - void init(); - void reportState(vstd::CLoggerBase * out) override; + /// Non-owning pointer to underlying connection + std::weak_ptr networkConnection; +// void init(); +// void reportState(vstd::CLoggerBase * out) override; +// int write(const void * data, unsigned size) override; int read(void * data, unsigned size) override; - void flushBuffers(); - - std::shared_ptr io_service; //can be empty if connection made from socket - - bool enableBufferedWrite; - bool enableBufferedRead; - std::unique_ptr connectionBuffers; +// void flushBuffers(); +// +// bool enableBufferedWrite; +// bool enableBufferedRead; +// std::unique_ptr connectionBuffers; +// + std::unique_ptr iser; + std::unique_ptr oser; +// +// std::string contactUuid; +// std::string name; //who uses this connection public: - BinaryDeserializer iser; - BinarySerializer oser; - - std::shared_ptr mutexRead; - std::shared_ptr mutexWrite; - std::shared_ptr socket; - bool connected; - bool myEndianess, contactEndianess; //true if little endian, if endianness is different we'll have to revert received multi-byte vars - std::string contactUuid; - std::string name; //who uses this connection std::string uuid; - int connectionID; - std::shared_ptr handler; - CConnection(const std::string & host, ui16 port, std::string Name, std::string UUID); - CConnection(const std::shared_ptr & acceptor, const std::shared_ptr & Io_service, std::string Name, std::string UUID); - CConnection(std::shared_ptr Socket, std::string Name, std::string UUID); //use immediately after accepting connection into socket + CConnection(std::weak_ptr networkConnection); +// CConnection(const std::string & host, ui16 port, std::string Name, std::string UUID); +// CConnection(const std::shared_ptr & acceptor, const std::shared_ptr & Io_service, std::string Name, std::string UUID); +// CConnection(std::shared_ptr Socket, std::string Name, std::string UUID); //use immediately after accepting connection into socket +// virtual ~CConnection(); - void close(); - bool isOpen() const; - template - CConnection &operator&(const T&); - virtual ~CConnection(); - - CPack * retrievePack(); +// void close(); +// bool isOpen() const; +// +// CPack * retrievePack(); void sendPack(const CPack * pack); + CPack * retrievePack(const std::vector & data); +// std::vector serializePack(const CPack * pack); +// void disableStackSendingByID(); - void enableStackSendingByID(); - void disableSmartPointerSerialization(); - void enableSmartPointerSerialization(); - void disableSmartVectorMemberSerialization(); - void enableSmartVectorMemberSerializatoin(); - +// void enableStackSendingByID(); +// void disableSmartPointerSerialization(); +// void enableSmartPointerSerialization(); +// void disableSmartVectorMemberSerialization(); +// void enableSmartVectorMemberSerializatoin(); +// void enterLobbyConnectionMode(); void enterGameplayConnectionMode(CGameState * gs); - - std::string toString() const; - - template - CConnection & operator>>(T &t) - { - iser & t; - return * this; - } - - template - CConnection & operator<<(const T &t) - { - oser & t; - return * this; - } +// +// std::string toString() const; +// +// template +// CConnection & operator>>(T &t) +// { +// iser & t; +// return * this; +// } +// +// template +// CConnection & operator<<(const T &t) +// { +// oser & t; +// return * this; +// } }; VCMI_LIB_NAMESPACE_END diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 0d1c9c183..a9eabb3ef 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -104,14 +104,14 @@ void LobbyServer::sendMessage(const std::shared_ptr & target, std::vector payloadBuffer(payloadBegin, payloadEnd); - sendPacket(target, payloadBuffer); + networkServer->sendPacket(target, payloadBuffer); } void LobbyServer::onNewConnection(const std::shared_ptr & connection) { } -void LobbyServer::onConnectionLost(const std::shared_ptr & connection) +void LobbyServer::onDisconnected(const std::shared_ptr & connection) { activeAccounts.erase(connection); } @@ -169,9 +169,20 @@ void LobbyServer::onPacketReceived(const std::shared_ptr & co LobbyServer::LobbyServer() : database(new LobbyDatabase()) + , networkServer(new NetworkServer(*this)) { } +void LobbyServer::start(uint16_t port) +{ + networkServer->start(port); +} + +void LobbyServer::run() +{ + networkServer->run(); +} + int main(int argc, const char * argv[]) { LobbyServer server; diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index ceaad47bc..8ebc0ba55 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -41,7 +41,7 @@ public: std::vector getRecentMessageHistory(); }; -class LobbyServer : public NetworkServer +class LobbyServer : public INetworkServerListener { struct AccountState { @@ -51,12 +51,16 @@ class LobbyServer : public NetworkServer std::map, AccountState> activeAccounts; std::unique_ptr database; + std::unique_ptr networkServer; void onNewConnection(const std::shared_ptr &) override; - void onConnectionLost(const std::shared_ptr &) override; + void onDisconnected(const std::shared_ptr &) override; void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; void sendMessage(const std::shared_ptr & target, const JsonNode & json); public: LobbyServer(); + + void start(uint16_t port); + void run(); }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index cc00e345a..5c0f8edb5 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -61,6 +61,7 @@ #include "../lib/serializer/CSaveFile.h" #include "../lib/serializer/CLoadFile.h" +#include "../lib/serializer/Connection.h" #include "../lib/spells/CSpellHandler.h" @@ -969,11 +970,11 @@ void CGameHandler::onNewTurn() synchronizeArtifactHandlerLists(); //new day events may have changed them. TODO better of managing that } -void CGameHandler::run(bool resume) +void CGameHandler::start(bool resume) { LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume); - for (auto cc : lobby->connections) + for (auto cc : lobby->activeConnections) { auto players = lobby->getAllClientPlayers(cc->connectionID); std::stringstream sbuffer; @@ -1004,18 +1005,11 @@ void CGameHandler::run(bool resume) events::GameResumed::defaultExecute(serverEventBus.get()); turnOrder->onGameStarted(); +} - //wait till game is done - auto clockLast = std::chrono::steady_clock::now(); - while(lobby->getState() == EServerState::GAMEPLAY) - { - const auto clockDuration = std::chrono::steady_clock::now() - clockLast; - const int timePassed = std::chrono::duration_cast(clockDuration).count(); - clockLast += clockDuration; - turnTimerHandler.update(timePassed); - boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); - - } +void CGameHandler::tick(int millisecondsPassed) +{ + turnTimerHandler.update(millisecondsPassed); } void CGameHandler::giveSpells(const CGTownInstance *t, const CGHeroInstance *h) @@ -1677,13 +1671,8 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) void CGameHandler::sendToAllClients(CPackForClient * pack) { logNetwork->trace("\tSending to all clients: %s", typeid(*pack).name()); - for (auto c : lobby->connections) - { - if(!c->isOpen()) - continue; - + for (auto c : lobby->activeConnections) c->sendPack(pack); - } } void CGameHandler::sendAndApply(CPackForClient * pack) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 152452d6a..38ca76e12 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -261,7 +261,8 @@ public: bool isPlayerOwns(CPackForServer * pack, ObjectInstanceID id); - void run(bool resume); + void start(bool resume); + void tick(int millisecondsPassed); bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector & slot); void spawnWanderingMonsters(CreatureID creatureID); diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 8f4ecaf75..c88941625 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -8,12 +8,11 @@ * */ #include "StdInc.h" -#include +#include #include "../lib/filesystem/Filesystem.h" #include "../lib/campaign/CampaignState.h" #include "../lib/CThreadHelper.h" -#include "../lib/serializer/Connection.h" #include "../lib/CArtHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" @@ -37,12 +36,15 @@ #include "CGameHandler.h" #include "processors/PlayerMessageProcessor.h" #include "../lib/mapping/CMapInfo.h" +#include "../lib/network/NetworkServer.h" +#include "../lib/network/NetworkClient.h" #include "../lib/GameConstants.h" #include "../lib/logging/CBasicLogConfigurator.h" #include "../lib/CConfigHandler.h" #include "../lib/ScopeGuard.h" #include "../lib/serializer/CMemorySerializer.h" #include "../lib/serializer/Cast.h" +#include "../lib/serializer/Connection.h" #include "../lib/UnlockGuard.h" @@ -81,11 +83,8 @@ public: if(checker.getResult()) { - boost::unique_lock stateLock(srv->stateMutex); ApplyOnServerNetPackVisitor applier(*srv); - ptr->visit(applier); - return applier.getResult(); } else @@ -117,48 +116,101 @@ public: } }; +class CVCMIServerPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) +{ +private: + CVCMIServer & handler; + std::shared_ptr gh; + +public: + CVCMIServerPackVisitor(CVCMIServer & handler, std::shared_ptr gh) + :handler(handler), gh(gh) + { + } + + virtual bool callTyped() override { return false; } + + virtual void visitForLobby(CPackForLobby & packForLobby) override + { + handler.handleReceivedPack(std::unique_ptr(&packForLobby)); + } + + virtual void visitForServer(CPackForServer & serverPack) override + { + if (gh) + gh->handleReceivedPack(&serverPack); + else + logNetwork->error("Received pack for game server while in lobby!"); + } + + virtual void visitForClient(CPackForClient & clientPack) override + { + } +}; + std::string SERVER_NAME_AFFIX = "server"; std::string SERVER_NAME = GameConstants::VCMI_VERSION + std::string(" (") + SERVER_NAME_AFFIX + ')'; CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts) - : port(3030), io(std::make_shared()), state(EServerState::LOBBY), cmdLineOptions(opts), currentClientId(1), currentPlayerId(1), restartGameplay(false) + : state(EServerState::LOBBY), cmdLineOptions(opts), currentClientId(1), currentPlayerId(1), restartGameplay(false) { uuid = boost::uuids::to_string(boost::uuids::random_generator()()); logNetwork->trace("CVCMIServer created! UUID: %s", uuid); applier = std::make_shared>(); registerTypesLobbyPacks(*applier); + uint16_t port = 3030; if(cmdLineOptions.count("port")) - port = cmdLineOptions["port"].as(); + port = cmdLineOptions["port"].as(); logNetwork->info("Port %d will be used", port); - try - { - acceptor = std::make_shared(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)); - } - catch(...) - { - logNetwork->info("Port %d is busy, trying to use random port instead", port); - if(cmdLineOptions.count("run-by-client")) - { - logNetwork->error("Port must be specified when run-by-client is used!!"); -#if (defined(__ANDROID_API__) && __ANDROID_API__ < 21) || (defined(__MINGW32__)) || defined(VCMI_APPLE) - ::exit(0); -#else - std::quick_exit(0); -#endif - } - acceptor = std::make_shared(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0)); - port = acceptor->local_endpoint().port(); - } + + networkServer = std::make_unique(*this); + networkServer->start(port); logNetwork->info("Listening for connections at port %d", port); } -CVCMIServer::~CVCMIServer() -{ - announceQueue.clear(); +CVCMIServer::~CVCMIServer() = default; - if(announceLobbyThread) - announceLobbyThread->join(); +void CVCMIServer::onNewConnection(const std::shared_ptr & connection) +{ + if (activeConnections.empty()) + establishOutgoingConnection(); + + if(state == EServerState::LOBBY) + activeConnections.push_back(std::make_shared(connection));//, SERVER_NAME, uuid);) + // TODO: else: deny connection + // TODO: else: try to reconnect / send state to reconnected client +} + +void CVCMIServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) +{ + std::shared_ptr c = findConnection(connection); + CPack * pack = c->retrievePack(message); + pack->c = c; + CVCMIServerPackVisitor visitor(*this, this->gh); + pack->visit(visitor); + + //FIXME: delete pack? +} + +void CVCMIServer::onPacketReceived(const std::vector & message) +{ + //TODO: handle pack received from lobby +} + +void CVCMIServer::onConnectionFailed(const std::string & errorMessage) +{ + //TODO: handle failure to connect to lobby +} + +void CVCMIServer::onConnectionEstablished() +{ + //TODO: handle connection to lobby - login? +} + +void CVCMIServer::onDisconnected() +{ + //TODO: handle disconnection from lobby } void CVCMIServer::setState(EServerState value) @@ -171,101 +223,60 @@ EServerState CVCMIServer::getState() const return state.load(); } +std::shared_ptr CVCMIServer::findConnection(const std::shared_ptr & netConnection) +{ + //TODO + assert(0); + return nullptr; +} + void CVCMIServer::run() { +#if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP) if(!restartGameplay) { - this->announceLobbyThread = std::make_unique(&CVCMIServer::threadAnnounceLobby, this); - - startAsyncAccept(); - if(!remoteConnectionsThread && cmdLineOptions.count("lobby")) - { - remoteConnectionsThread = std::make_unique(&CVCMIServer::establishRemoteConnections, this); - } - -#if defined(VCMI_ANDROID) -#ifndef SINGLE_PROCESS_APP CAndroidVMHelper vmHelper; vmHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady"); + } #endif -#endif - } - while(state == EServerState::LOBBY || state == EServerState::GAMEPLAY_STARTING) - boost::this_thread::sleep_for(boost::chrono::milliseconds(50)); + static const int serverUpdateIntervalMilliseconds = 50; + auto clockInitial = std::chrono::steady_clock::now(); + int64_t msPassedLast = 0; - logNetwork->info("Thread handling connections ended"); - - if(state == EServerState::GAMEPLAY) - { - gh->run(si->mode == StartInfo::LOAD_GAME); - } - while(state == EServerState::GAMEPLAY_ENDED) - boost::this_thread::sleep_for(boost::chrono::milliseconds(50)); -} - -void CVCMIServer::establishRemoteConnections() -{ - setThreadName("establishConnection"); - - //wait for host connection - while(connections.empty()) - boost::this_thread::sleep_for(boost::chrono::milliseconds(50)); - - uuid = cmdLineOptions["lobby-uuid"].as(); - int numOfConnections = cmdLineOptions["connections"].as(); - for(int i = 0; i < numOfConnections; ++i) - connectToRemote(); -} - -void CVCMIServer::connectToRemote() -{ - std::shared_ptr c; - try - { - auto address = cmdLineOptions["lobby"].as(); - int port = cmdLineOptions["lobby-port"].as(); - - logNetwork->info("Establishing connection to remote at %s:%d with uuid %s", address, port, uuid); - c = std::make_shared(address, port, SERVER_NAME, uuid); - } - catch(...) - { - logNetwork->error("\nCannot establish remote connection!"); - } - - if(c) - { - connections.insert(c); - remoteConnections.insert(c); - c->handler = std::make_shared(&CVCMIServer::threadHandleClient, this, c); - } -} - -void CVCMIServer::threadAnnounceLobby() -{ - setThreadName("announceLobby"); while(state != EServerState::SHUTDOWN) { - { - boost::unique_lock myLock(mx); - while(!announceQueue.empty()) - { - announcePack(std::move(announceQueue.front())); - announceQueue.pop_front(); - } + networkServer->run(std::chrono::milliseconds(serverUpdateIntervalMilliseconds)); - if(acceptor) - { - io->reset(); - io->poll(); - } - } + const auto clockNow = std::chrono::steady_clock::now(); + const auto clockPassed = clockNow - clockInitial; + const int64_t msPassedNow = std::chrono::duration_cast(clockPassed).count(); + const int64_t msDelta = msPassedNow - msPassedLast; + msPassedLast = msPassedNow; - boost::this_thread::sleep_for(boost::chrono::milliseconds(50)); + if (state == EServerState::GAMEPLAY) + gh->tick(msDelta); } } +void CVCMIServer::establishOutgoingConnection() +{ + if(!cmdLineOptions.count("lobby")) + return; + + uuid = cmdLineOptions["lobby-uuid"].as(); + auto address = cmdLineOptions["lobby"].as(); + int port = cmdLineOptions["lobby-port"].as(); + logNetwork->info("Establishing connection to remote at %s:%d with uuid %s", address, port, uuid); + + outgoingConnection = std::make_unique(*this); + + outgoingConnection->start(address, port);//, SERVER_NAME, uuid); + +// connections.insert(c); +// remoteConnections.insert(c); +} + void CVCMIServer::prepareToRestart() { if(state == EServerState::GAMEPLAY) @@ -280,11 +291,9 @@ void CVCMIServer::prepareToRestart() campaignMap = si->campState->currentScenario().value_or(CampaignScenarioID(0)); campaignBonus = si->campState->getBonusID(campaignMap).value_or(-1); } - // FIXME: dirry hack to make sure old CGameHandler::run is finished - boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); } - for(auto c : connections) + for(auto c : activeConnections) { c->enterLobbyConnectionMode(); c->disableStackSendingByID(); @@ -306,10 +315,11 @@ bool CVCMIServer::prepareToStartGame() { if(progressTracking.get() != currentProgress) { + //FIXME: UNGUARDED MULTITHREADED ACCESS!!! currentProgress = progressTracking.get(); std::unique_ptr loadProgress(new LobbyLoadProgress); loadProgress->progress = currentProgress; - addToAnnounceQueue(std::move(loadProgress)); + announcePack(std::move(loadProgress)); } boost::this_thread::sleep(boost::posix_time::milliseconds(50)); } @@ -355,149 +365,55 @@ bool CVCMIServer::prepareToStartGame() return true; } -void CVCMIServer::startGameImmidiately() +void CVCMIServer::startGameImmediately() { - for(auto c : connections) + for(auto c : activeConnections) c->enterGameplayConnectionMode(gh->gs); + gh->start(si->mode == StartInfo::LOAD_GAME); state = EServerState::GAMEPLAY; } -void CVCMIServer::startAsyncAccept() +void CVCMIServer::onDisconnected(const std::shared_ptr & connection) { - assert(!upcomingConnection); - assert(acceptor); + logNetwork->error("Network error receiving a pack. Connection has been closed"); -#if BOOST_VERSION >= 107000 // Boost version >= 1.70 - upcomingConnection = std::make_shared(acceptor->get_executor()); -#else - upcomingConnection = std::make_shared(acceptor->get_io_service()); -#endif - acceptor->async_accept(*upcomingConnection, std::bind(&CVCMIServer::connectionAccepted, this, _1)); -} + std::shared_ptr c = findConnection(connection); -void CVCMIServer::connectionAccepted(const boost::system::error_code & ec) -{ - if(ec) + inactiveConnections.push_back(c); + vstd::erase(activeConnections, c); + + if(activeConnections.empty() || hostClientId == c->connectionID) + state = EServerState::SHUTDOWN; + + if(gh && state == EServerState::GAMEPLAY) { - if(state != EServerState::SHUTDOWN) - logNetwork->info("Something wrong during accepting: %s", ec.message()); - return; - } - - try - { - if(state == EServerState::LOBBY || !hangingConnections.empty()) - { - logNetwork->info("We got a new connection! :)"); - auto c = std::make_shared(upcomingConnection, SERVER_NAME, uuid); - upcomingConnection.reset(); - connections.insert(c); - c->handler = std::make_shared(&CVCMIServer::threadHandleClient, this, c); - } - } - catch(std::exception & e) - { - logNetwork->error("Failure processing new connection! %s", e.what()); - upcomingConnection.reset(); - } - - startAsyncAccept(); -} - -class CVCMIServerPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) -{ -private: - CVCMIServer & handler; - std::shared_ptr gh; - -public: - CVCMIServerPackVisitor(CVCMIServer & handler, std::shared_ptr gh) - :handler(handler), gh(gh) - { - } - - virtual bool callTyped() override { return false; } - - virtual void visitForLobby(CPackForLobby & packForLobby) override - { - handler.handleReceivedPack(std::unique_ptr(&packForLobby)); - } - - virtual void visitForServer(CPackForServer & serverPack) override - { - if (gh) - gh->handleReceivedPack(&serverPack); - else - logNetwork->error("Received pack for game server while in lobby!"); - } - - virtual void visitForClient(CPackForClient & clientPack) override - { - } -}; - -void CVCMIServer::threadHandleClient(std::shared_ptr c) -{ - setThreadName("handleClient"); - c->enterLobbyConnectionMode(); - - while(c->connected) - { - CPack * pack; - - try - { - pack = c->retrievePack(); - pack->c = c; - } - catch(boost::system::system_error & e) - { - if (e.code() == boost::asio::error::eof) - logNetwork->error("Network error receiving a pack. Connection has been closed"); - else - logNetwork->error("Network error receiving a pack. Connection %s dies. What happened: %s", c->toString(), e.what()); - - hangingConnections.insert(c); - connections.erase(c); - if(connections.empty() || hostClient == c) - state = EServerState::SHUTDOWN; - - if(gh && state == EServerState::GAMEPLAY) - { - gh->handleClientDisconnection(c); - } - break; - } - - CVCMIServerPackVisitor visitor(*this, this->gh); - pack->visit(visitor); + gh->handleClientDisconnection(c); } boost::unique_lock queueLock(mx); - if(c->connected) - { - auto lcd = std::make_unique(); - lcd->c = c; - lcd->clientId = c->connectionID; - handleReceivedPack(std::move(lcd)); - } - - logNetwork->info("Thread listening for %s ended", c->toString()); - c->handler.reset(); +// if(c->connected) +// { +// auto lcd = std::make_unique(); +// lcd->c = c; +// lcd->clientId = c->connectionID; +// handleReceivedPack(std::move(lcd)); +// } +// +// logNetwork->info("Thread listening for %s ended", c->toString()); } void CVCMIServer::handleReceivedPack(std::unique_ptr pack) { CBaseForServerApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack.get())); if(apply->applyOnServerBefore(this, pack.get())) - addToAnnounceQueue(std::move(pack)); + announcePack(std::move(pack)); } void CVCMIServer::announcePack(std::unique_ptr pack) { - for(auto c : connections) + for(auto c : activeConnections) { // FIXME: we need to avoid sending something to client that not yet get answer for LobbyClientConnected // Until UUID set we only pass LobbyClientConnected to this client @@ -515,7 +431,7 @@ void CVCMIServer::announceMessage(const std::string & txt) logNetwork->info("Show message: %s", txt); auto cm = std::make_unique(); cm->message = txt; - addToAnnounceQueue(std::move(cm)); + announcePack(std::move(cm)); } void CVCMIServer::announceTxt(const std::string & txt, const std::string & playerName) @@ -524,25 +440,18 @@ void CVCMIServer::announceTxt(const std::string & txt, const std::string & playe auto cm = std::make_unique(); cm->playerName = playerName; cm->message = txt; - addToAnnounceQueue(std::move(cm)); -} - -void CVCMIServer::addToAnnounceQueue(std::unique_ptr pack) -{ - boost::unique_lock queueLock(mx); - announceQueue.push_back(std::move(pack)); + announcePack(std::move(cm)); } bool CVCMIServer::passHost(int toConnectionId) { - for(auto c : connections) + for(auto c : activeConnections) { if(isClientHost(c->connectionID)) continue; if(c->connectionID != toConnectionId) continue; - hostClient = c; hostClientId = c->connectionID; announceTxt(boost::str(boost::format("Pass host to connection %d") % toConnectionId)); return true; @@ -555,9 +464,8 @@ void CVCMIServer::clientConnected(std::shared_ptr c, std::vectorconnectionID = currentClientId++; - if(!hostClient) + if(hostClientId == -1) { - hostClient = c; hostClientId = c->connectionID; si->mode = mode; } @@ -592,8 +500,9 @@ void CVCMIServer::clientConnected(std::shared_ptr c, std::vector c) { - connections -= c; - if(connections.empty() || hostClient == c) + vstd::erase(activeConnections, c); + + if(activeConnections.empty() || hostClientId == c->connectionID) { state = EServerState::SHUTDOWN; return; @@ -626,7 +535,7 @@ void CVCMIServer::clientDisconnected(std::shared_ptr c) if(gh && si && state == EServerState::GAMEPLAY) { gh->playerMessages->broadcastMessage(playerSettings->color, playerLeftMsgText); - gh->connections[playerSettings->color].insert(hostClient); + // gh->connections[playerSettings->color].insert(hostClient); startAiPack.players.push_back(playerSettings->color); } } @@ -753,7 +662,6 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr mapInfo, void CVCMIServer::updateAndPropagateLobbyState() { - boost::unique_lock stateLock(stateMutex); // Update player settings for RMG // TODO: find appropriate location for this code if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME) @@ -772,7 +680,7 @@ void CVCMIServer::updateAndPropagateLobbyState() auto lus = std::make_unique(); lus->state = *this; - addToAnnounceQueue(std::move(lus)); + announcePack(std::move(lus)); } void CVCMIServer::setPlayer(PlayerColor clickedColor) @@ -1188,32 +1096,9 @@ int main(int argc, const char * argv[]) cond->notify_one(); #endif - try - { - boost::asio::io_service io_service; - CVCMIServer server(opts); + CVCMIServer server(opts); + server.run(); - try - { - while(server.getState() != EServerState::SHUTDOWN) - { - server.run(); - } - io_service.run(); - } - catch(boost::system::system_error & e) //for boost errors just log, not crash - probably client shut down connection - { - logNetwork->error(e.what()); - server.setState(EServerState::SHUTDOWN); - } - } - catch(boost::system::system_error & e) - { - logNetwork->error(e.what()); - //catch any startup errors (e.g. can't access port) errors - //and return non-zero status so client can detect error - throw; - } #if VCMI_ANDROID_DUAL_PROCESS CAndroidVMHelper envHelper; envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer"); diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 1bcac979a..d83c3edf0 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -9,10 +9,10 @@ */ #pragma once -#include "../lib/serializer/Connection.h" +#include "../lib/network/NetworkListener.h" #include "../lib/StartInfo.h" -#include +#include #if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP) #define VCMI_ANDROID_DUAL_PROCESS 1 @@ -24,6 +24,7 @@ class CMapInfo; struct CPackForLobby; +class CConnection; struct StartInfo; struct LobbyInfo; struct PlayerSettings; @@ -46,53 +47,66 @@ enum class EServerState : ui8 SHUTDOWN }; -class CVCMIServer : public LobbyInfo +class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INetworkClientListener { + /// Network server instance that receives and processes incoming connections on active socket + std::unique_ptr networkServer; + + /// Outgoing connection established by this server to game lobby for proxy mode (only in lobby game) + std::unique_ptr outgoingConnection; + +public: + /// List of all active connections + std::vector> activeConnections; + +private: + /// List of all connections that were closed (but can still reconnect later) + std::vector> inactiveConnections; + std::atomic restartGameplay; // FIXME: this is just a hack - std::shared_ptr io; - std::shared_ptr acceptor; - std::shared_ptr upcomingConnection; - std::list> announceQueue; + boost::recursive_mutex mx; std::shared_ptr> applier; - std::unique_ptr announceLobbyThread; std::unique_ptr remoteConnectionsThread; std::atomic state; -public: - std::shared_ptr gh; - ui16 port; + // INetworkServerListener impl + void onDisconnected(const std::shared_ptr & connection) override; + void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; + void onNewConnection(const std::shared_ptr &) override; + + // INetworkClientListener impl + void onPacketReceived(const std::vector & message) override; + void onConnectionFailed(const std::string & errorMessage) override; + void onConnectionEstablished() override; + void onDisconnected() override; + + void establishOutgoingConnection(); + + std::shared_ptr findConnection(const std::shared_ptr &); - boost::program_options::variables_map cmdLineOptions; - std::set> connections; - std::set> remoteConnections; - std::set> hangingConnections; //keep connections of players disconnected during the game - std::atomic currentClientId; std::atomic currentPlayerId; - std::shared_ptr hostClient; + +public: + std::shared_ptr gh; + boost::program_options::variables_map cmdLineOptions; CVCMIServer(boost::program_options::variables_map & opts); ~CVCMIServer(); + void run(); + bool prepareToStartGame(); void prepareToRestart(); - void startGameImmidiately(); + void startGameImmediately(); - void establishRemoteConnections(); - void connectToRemote(); - void startAsyncAccept(); - void connectionAccepted(const boost::system::error_code & ec); void threadHandleClient(std::shared_ptr c); - void threadAnnounceLobby(); - void handleReceivedPack(std::unique_ptr pack); void announcePack(std::unique_ptr pack); bool passHost(int toConnectionId); void announceTxt(const std::string & txt, const std::string & playerName = "system"); - void announceMessage(const std::string & txt); - void addToAnnounceQueue(std::unique_ptr pack); void setPlayerConnectedId(PlayerSettings & pset, ui8 player) const; void updateStartInfoOnMapChange(std::shared_ptr mapInfo, std::shared_ptr mapGenOpt = {}); @@ -101,6 +115,11 @@ public: void clientDisconnected(std::shared_ptr c); void reconnectPlayer(int connId); +public: + void announceMessage(const std::string & txt); + + void handleReceivedPack(std::unique_ptr pack); + void updateAndPropagateLobbyState(); void setState(EServerState value); diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index bb99323a5..254d20b22 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -13,11 +13,9 @@ #include "CVCMIServer.h" #include "CGameHandler.h" -#include "../lib/serializer/Connection.h" #include "../lib/StartInfo.h" - -// Campaigns #include "../lib/campaign/CampaignState.h" +#include "../lib/serializer/Connection.h" void ClientPermissionsCheckerNetPackVisitor::visitForLobby(CPackForLobby & pack) { @@ -38,67 +36,18 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitForLobby(CPackForLobby & pac void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack) { - if(srv.gh) - { - for(auto & connection : srv.hangingConnections) - { - if(connection->uuid == pack.uuid) - { - { - result = true; - return; - } - } - } - } - if(srv.getState() == EServerState::LOBBY) { result = true; return; } - //disconnect immediately and ignore this client - srv.connections.erase(pack.c); - if(pack.c && pack.c->isOpen()) - { - pack.c->close(); - pack.c->connected = false; - } - { result = false; return; - } } void ApplyOnServerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack) { - if(srv.gh) - { - for(auto & connection : srv.hangingConnections) - { - if(connection->uuid == pack.uuid) - { - logNetwork->info("Reconnection player"); - pack.c->connectionID = connection->connectionID; - for(auto & playerConnection : srv.gh->connections) - { - for(auto & existingConnection : playerConnection.second) - { - if(existingConnection == connection) - { - playerConnection.second.erase(existingConnection); - playerConnection.second.insert(pack.c); - break; - } - } - } - srv.hangingConnections.erase(connection); - break; - } - } - } - srv.clientConnected(pack.c, pack.names, pack.uuid, pack.mode); // Server need to pass some data to newly connected client pack.clientId = pack.c->connectionID; @@ -121,7 +70,7 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientConnected(LobbyCl startGameForReconnectedPlayer->initializedStartInfo = srv.si; startGameForReconnectedPlayer->initializedGameState = srv.gh->gameState(); startGameForReconnectedPlayer->clientId = pack.c->connectionID; - srv.addToAnnounceQueue(std::move(startGameForReconnectedPlayer)); + srv.announcePack(std::move(startGameForReconnectedPlayer)); } } @@ -154,38 +103,28 @@ void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientDisconnected(LobbyC void ApplyOnServerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) { srv.clientDisconnected(pack.c); - pack.c->close(); - pack.c->connected = false; - result = true; } void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) { - if(pack.c && pack.c->isOpen()) - { - boost::unique_lock lock(*pack.c->mutexWrite); - pack.c->close(); - pack.c->connected = false; - } - if(pack.shutdownServer) { logNetwork->info("Client requested shutdown, server will close itself..."); srv.setState(EServerState::SHUTDOWN); return; } - else if(srv.connections.empty()) + else if(srv.activeConnections.empty()) { logNetwork->error("Last connection lost, server will close itself..."); srv.setState(EServerState::SHUTDOWN); } - else if(pack.c == srv.hostClient) + else if(pack.c->connectionID == srv.hostClientId) { auto ph = std::make_unique(); - auto newHost = *RandomGeneratorUtil::nextItem(srv.connections, CRandomGenerator::getDefault()); + auto newHost = srv.activeConnections.front(); ph->newHostConnectionId = newHost->connectionID; - srv.addToAnnounceQueue(std::move(ph)); + srv.announcePack(std::move(ph)); } srv.updateAndPropagateLobbyState(); @@ -270,8 +209,7 @@ void ApplyOnServerNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack) void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack) { - boost::unique_lock stateLock(srv.stateMutex); - for(auto & c : srv.connections) + for(auto & c : srv.activeConnections) { c->enterLobbyConnectionMode(); c->disableStackSendingByID(); @@ -312,10 +250,10 @@ void ApplyOnServerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) { if(pack.clientId == -1) //do not restart game for single client only - srv.startGameImmidiately(); + srv.startGameImmediately(); else { - for(auto & c : srv.connections) + for(auto & c : srv.activeConnections) { if(c->connectionID == pack.clientId) { diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index a3d77db31..95287bfe8 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -20,11 +20,11 @@ #include "../lib/IGameCallback.h" #include "../lib/mapObjects/CGTownInstance.h" +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/gameState/CGameState.h" #include "../lib/battle/IBattleState.h" #include "../lib/battle/BattleAction.h" #include "../lib/battle/Unit.h" -#include "../lib/serializer/Connection.h" #include "../lib/spells/CSpellHandler.h" #include "../lib/spells/ISpellMechanics.h" #include "../lib/serializer/Cast.h" diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index 04b4456a3..4bd176f7a 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -13,7 +13,6 @@ #include "../CGameHandler.h" #include "../CVCMIServer.h" -#include "../../lib/serializer/Connection.h" #include "../../lib/CGeneralTextHandler.h" #include "../../lib/CHeroHandler.h" #include "../../lib/modding/IdentifierStorage.h" @@ -22,11 +21,13 @@ #include "../../lib/StartInfo.h" #include "../../lib/gameState/CGameState.h" #include "../../lib/mapObjects/CGTownInstance.h" +#include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/modding/IdentifierStorage.h" #include "../../lib/modding/ModScope.h" #include "../../lib/mapping/CMap.h" #include "../../lib/networkPacks/PacksForClient.h" #include "../../lib/networkPacks/StackLocation.h" +#include "../../lib/serializer/Connection.h" PlayerMessageProcessor::PlayerMessageProcessor() :gameHandler(nullptr) @@ -62,10 +63,7 @@ bool PlayerMessageProcessor::handleHostCommand(PlayerColor player, const std::st std::vector words; boost::split(words, message, boost::is_any_of(" ")); - bool isHost = false; - for(auto & c : gameHandler->connections[player]) - if(gameHandler->gameLobby()->isClientHost(c->connectionID)) - isHost = true; + bool isHost = gameHandler->gameLobby()->isPlayerHost(player); if(!isHost || words.size() < 2 || words[0] != "game") return false; From 22f0ca67c6fe46c0a600ddc6d12ab4e8856d1fc3 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 25 Dec 2023 21:23:27 +0200 Subject: [PATCH 08/98] Fix connection to game lobby & map load --- client/CServerHandler.cpp | 57 ++-- client/CServerHandler.h | 12 +- client/NetPacksLobbyClient.cpp | 3 +- client/mainmenu/CMainMenu.cpp | 1 + client/serverLobby/LobbyWindow.cpp | 6 +- client/serverLobby/LobbyWindow.h | 6 +- lib/network/NetworkClient.cpp | 12 +- lib/network/NetworkClient.h | 1 + lib/network/NetworkConnection.cpp | 4 +- lib/network/NetworkConnection.h | 2 +- lib/network/NetworkListener.h | 6 +- lib/serializer/Connection.cpp | 453 +++++++---------------------- lib/serializer/Connection.h | 77 ++--- server/CVCMIServer.cpp | 33 +-- server/CVCMIServer.h | 8 +- server/NetPacksLobbyServer.cpp | 3 - 16 files changed, 211 insertions(+), 473 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index a725d2ed5..d41fe02ec 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -131,7 +131,11 @@ public: static const std::string NAME_AFFIX = "client"; static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name -CServerHandler::~CServerHandler() = default; +CServerHandler::~CServerHandler() +{ + networkClient->stop(); + threadNetwork->join(); +} CServerHandler::CServerHandler() : state(EClientState::NONE) @@ -148,6 +152,16 @@ CServerHandler::CServerHandler() uuid = settings["server"]["uuid"].String(); applier = std::make_shared>(); registerTypesLobbyPacks(*applier); + + threadNetwork = std::make_unique(&CServerHandler::threadRunNetwork, this); +} + +void CServerHandler::threadRunNetwork() +{ + logGlobal->info("Starting network thread"); + setThreadName("runNetwork"); + networkClient->run(); + logGlobal->info("Ending network thread"); } void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::vector * names) @@ -178,12 +192,13 @@ void CServerHandler::startLocalServerAndConnect(const std::function & on auto errorMsg = CGI->generaltexth->translate("vcmi.server.errors.existingProcess"); - if (!checkNetworkPortIsFree(localhostAddress, getDefaultPort())) - { - logNetwork->error("Port is busy, check if another instance of vcmiserver is working"); - CInfoWindow::showInfoDialog(errorMsg, {}); - return; - } +// TODO: restore +// if (!checkNetworkPortIsFree(localhostAddress, getDefaultPort())) +// { +// logNetwork->error("Port is busy, check if another instance of vcmiserver is working"); +// CInfoWindow::showInfoDialog(errorMsg, {}); +// return; +// } #if defined(SINGLE_PROCESS_APP) boost::condition_variable cond; @@ -195,7 +210,7 @@ void CServerHandler::startLocalServerAndConnect(const std::function & on args.push_back("--lobby-port=" + std::to_string(settings["session"]["port"].Integer())); args.push_back("--lobby-uuid=" + settings["session"]["hostUuid"].String()); } - threadRunLocalServer = std::make_shared([&cond, args, this] { + threadRunLocalServer = std::make_unique([&cond, args, this] { setThreadName("CVCMIServer"); CVCMIServer::create(&cond, args); onServerFinished(); @@ -207,7 +222,7 @@ void CServerHandler::startLocalServerAndConnect(const std::function & on envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "startServer", true); } #else - threadRunLocalServer = std::make_shared(&CServerHandler::threadRunServer, this); //runs server executable; + threadRunLocalServer = std::make_unique(&CServerHandler::threadRunServer, this); //runs server executable; #endif logNetwork->trace("Setting up thread calling server: %d ms", th->getDiff()); @@ -268,6 +283,10 @@ void CServerHandler::justConnectToServer(const std::string & addr, const ui16 po serverPort->Integer() = port; } + if (onConnectedCallback) + throw std::runtime_error("Attempt to connect while there is already a pending connection!"); + + onConnectedCallback = onConnected; networkClient->start(addr.size() ? addr : getHostAddress(), port ? port : getHostPort()); } @@ -288,14 +307,18 @@ void CServerHandler::onConnectionFailed(const std::string & errorMessage) networkClient->start(getHostAddress(), getHostPort()); } -void CServerHandler::onConnectionEstablished() +void CServerHandler::onConnectionEstablished(const std::shared_ptr & netConnection) { + logNetwork->info("Connection established"); + c = std::make_shared(netConnection); c->enterLobbyConnectionMode(); sendClientConnecting(); - //FIXME: call functor provided in CServerHandler::justConnectToServer - assert(0); - //onConnected(); + if (onConnectedCallback) + { + onConnectedCallback(); + onConnectedCallback = {}; + } } void CServerHandler::applyPacksOnLobbyScreen() @@ -620,7 +643,6 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const } sendLobbyPack(lsg); c->enterLobbyConnectionMode(); - c->disableStackSendingByID(); } void CServerHandler::startMapAfterConnection(std::shared_ptr to) @@ -699,10 +721,7 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart) } if(c) - { c->enterLobbyConnectionMode(); - c->disableStackSendingByID(); - } //reset settings Settings saveSession = settings.write["server"]["reconnect"]; @@ -898,7 +917,7 @@ public: } }; -void CServerHandler::onPacketReceived(const std::vector & message) +void CServerHandler::onPacketReceived(const std::shared_ptr &, const std::vector & message) { CPack * pack = c->retrievePack(message); if(state == EClientState::DISCONNECTING) @@ -914,7 +933,7 @@ void CServerHandler::onPacketReceived(const std::vector & message) } } -void CServerHandler::onDisconnected() +void CServerHandler::onDisconnected(const std::shared_ptr &) { if(state == EClientState::DISCONNECTING) { diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 5192638e9..1a8ddd90e 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -98,14 +98,17 @@ class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClien std::shared_ptr highScoreCalc; + std::function onConnectedCallback; + + void threadRunNetwork(); void threadRunServer(); void onServerFinished(); void sendLobbyPack(const CPackForLobby & pack) const override; - void onPacketReceived(const std::vector & message) override; + void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; - void onConnectionEstablished() override; - void onDisconnected() override; + void onConnectionEstablished(const std::shared_ptr &) override; + void onDisconnected(const std::shared_ptr &) override; public: std::shared_ptr c; @@ -122,7 +125,8 @@ public: //////////////////// std::unique_ptr th; - std::shared_ptr threadRunLocalServer; + std::unique_ptr threadRunLocalServer; + std::unique_ptr threadNetwork; CClient * client; diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index 46173ca20..1bbdd448d 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -36,7 +36,8 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon result = false; // Check if it's LobbyClientConnected for our client - if(pack.uuid == handler.c->uuid) + // TODO: restore + //if(pack.uuid == handler.c->uuid) { handler.c->connectionID = pack.clientId; if(handler.mapToStart) diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index d4aa5705a..ce6087e92 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -615,6 +615,7 @@ void CSimpleJoinScreen::startConnection(const std::string & addr, ui16 port) { // async call to prevent thread race GH.dispatchMainThread([this](){ + // FIXME: this enum value is never set!!! if(CSH->state == EClientState::CONNECTION_FAILED) { CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {}); diff --git a/client/serverLobby/LobbyWindow.cpp b/client/serverLobby/LobbyWindow.cpp index 193c56f61..6d2363da4 100644 --- a/client/serverLobby/LobbyWindow.cpp +++ b/client/serverLobby/LobbyWindow.cpp @@ -42,7 +42,7 @@ static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0) return timeFormatted.toString(); } -void LobbyClient::onPacketReceived(const std::vector & message) +void LobbyClient::onPacketReceived(const std::shared_ptr &, const std::vector & message) { // FIXME: find better approach const char * payloadBegin = reinterpret_cast(message.data()); @@ -69,7 +69,7 @@ void LobbyClient::onPacketReceived(const std::vector & message) } } -void LobbyClient::onConnectionEstablished() +void LobbyClient::onConnectionEstablished(const std::shared_ptr &) { JsonNode toSend; toSend["type"].String() = "authentication"; @@ -84,7 +84,7 @@ void LobbyClient::onConnectionFailed(const std::string & errorMessage) CInfoWindow::showInfoDialog("Failed to connect to game lobby!\n" + errorMessage, {}); } -void LobbyClient::onDisconnected() +void LobbyClient::onDisconnected(const std::shared_ptr &) { GH.windows().popWindows(1); CInfoWindow::showInfoDialog("Connection to game lobby was lost!", {}); diff --git a/client/serverLobby/LobbyWindow.h b/client/serverLobby/LobbyWindow.h index 4121580e0..70b221751 100644 --- a/client/serverLobby/LobbyWindow.h +++ b/client/serverLobby/LobbyWindow.h @@ -32,10 +32,10 @@ class LobbyClient : public INetworkClientListener std::unique_ptr networkClient; LobbyWindow * window; - void onPacketReceived(const std::vector & message) override; + void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; - void onConnectionEstablished() override; - void onDisconnected() override; + void onConnectionEstablished(const std::shared_ptr &) override; + void onDisconnected(const std::shared_ptr &) override; public: explicit LobbyClient(LobbyWindow * window); diff --git a/lib/network/NetworkClient.cpp b/lib/network/NetworkClient.cpp index dbcbd9315..d67069b57 100644 --- a/lib/network/NetworkClient.cpp +++ b/lib/network/NetworkClient.cpp @@ -56,11 +56,12 @@ void NetworkClient::onConnected(const boost::system::error_code & ec) connection = std::make_shared(socket, *this); connection->start(); - listener.onConnectionEstablished(); + listener.onConnectionEstablished(connection); } void NetworkClient::run() { + boost::asio::executor_work_guardget_executor())> work{io->get_executor()}; io->run(); } @@ -69,6 +70,11 @@ void NetworkClient::poll() io->poll(); } +void NetworkClient::stop() +{ + io->stop(); +} + void NetworkClient::sendPacket(const std::vector & message) { connection->sendPacket(message); @@ -76,12 +82,12 @@ void NetworkClient::sendPacket(const std::vector & message) void NetworkClient::onDisconnected(const std::shared_ptr & connection) { - listener.onDisconnected(); + listener.onDisconnected(connection); } void NetworkClient::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) { - listener.onPacketReceived(message); + listener.onPacketReceived(connection, message); } diff --git a/lib/network/NetworkClient.h b/lib/network/NetworkClient.h index 541d34d23..2f80e9a40 100644 --- a/lib/network/NetworkClient.h +++ b/lib/network/NetworkClient.h @@ -42,6 +42,7 @@ public: void start(const std::string & host, uint16_t port); void run(); void poll(); + void stop(); }; VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index 5cb567f94..7c068147b 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -16,7 +16,9 @@ NetworkConnection::NetworkConnection(const std::shared_ptr & sock : socket(socket) , listener(listener) { - + socket->set_option(boost::asio::ip::tcp::no_delay(true)); + socket->set_option(boost::asio::socket_base::send_buffer_size(4194304)); + socket->set_option(boost::asio::socket_base::receive_buffer_size(4194304)); } void NetworkConnection::start() diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h index cb72903bd..ab315c7c5 100644 --- a/lib/network/NetworkConnection.h +++ b/lib/network/NetworkConnection.h @@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN class DLL_LINKAGE NetworkConnection :public std::enable_shared_from_this, boost::noncopyable { static const int messageHeaderSize = sizeof(uint32_t); - static const int messageMaxSize = 65536; // arbitrary size to prevent potential massive allocation if we receive garbage input + static const int messageMaxSize = 64 * 1024 * 1024; // arbitrary size to prevent potential massive allocation if we receive garbage input std::shared_ptr socket; diff --git a/lib/network/NetworkListener.h b/lib/network/NetworkListener.h index 7f3200ef5..6b9f3abc2 100644 --- a/lib/network/NetworkListener.h +++ b/lib/network/NetworkListener.h @@ -34,14 +34,12 @@ protected: ~INetworkServerListener() = default; }; -class DLL_LINKAGE INetworkClientListener +class DLL_LINKAGE INetworkClientListener : public INetworkConnectionListener { friend class NetworkClient; protected: - virtual void onPacketReceived(const std::vector & message) = 0; virtual void onConnectionFailed(const std::string & errorMessage) = 0; - virtual void onConnectionEstablished() = 0; - virtual void onDisconnected() = 0; + virtual void onConnectionEstablished(const std::shared_ptr &) = 0; ~INetworkClientListener() = default; }; diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index 8d7e8be2e..ad0dfd852 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -13,397 +13,150 @@ #include "BinaryDeserializer.h" #include "BinarySerializer.h" -//#include "../networkPacks/NetPacksBase.h" +#include "../networkPacks/NetPacksBase.h" +#include "../network/NetworkConnection.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class DLL_LINKAGE ConnectionPackWriter final : public IBinaryWriter +{ +public: + std::vector buffer; + + int write(const void * data, unsigned size) final; +}; + +class DLL_LINKAGE ConnectionPackReader final : public IBinaryReader +{ +public: + const std::vector * buffer; + size_t position; + + int read(void * data, unsigned size) final; +}; + +int ConnectionPackWriter::write(const void * data, unsigned size) +{ + const uint8_t * begin_ptr = static_cast(data); + const uint8_t * end_ptr = begin_ptr + size; + buffer.insert(buffer.end(), begin_ptr, end_ptr); + return size; +} + +int ConnectionPackReader::read(void * data, unsigned size) +{ + if (position + size > buffer->size()) + throw std::runtime_error("End of file reached when reading received network pack!"); + + uint8_t * begin_ptr = static_cast(data); + + std::copy_n(buffer->begin() + position, size, begin_ptr); + position += size; + return size; +} CConnection::CConnection(std::weak_ptr networkConnection) + : networkConnection(networkConnection) + , packReader(std::make_unique()) + , packWriter(std::make_unique()) + , deserializer(std::make_unique(packReader.get())) + , serializer(std::make_unique(packWriter.get())) + , connectionID(-1) { + assert(networkConnection.lock() != nullptr); + enableSmartPointerSerialization(); + disableStackSendingByID(); + deserializer->fileVersion = SERIALIZATION_VERSION; } +CConnection::~CConnection() = default; + void CConnection::sendPack(const CPack * pack) { + auto connectionPtr = networkConnection.lock(); + if (!connectionPtr) + throw std::runtime_error("Attempt to send packet on a closed connection!"); + + *serializer & pack; + + logNetwork->trace("Sending a pack of type %s", typeid(*pack).name()); + + connectionPtr->sendPacket(packWriter->buffer); + packWriter->buffer.clear(); } CPack * CConnection::retrievePack(const std::vector & data) { - return nullptr; + CPack * result; + + packReader->buffer = &data; + packReader->position = 0; + + *deserializer & result; + + logNetwork->trace("Received CPack of type %s", (result ? typeid(*result).name() : "nullptr")); + return result; +} + +bool CConnection::isMyConnection(const std::shared_ptr & otherConnection) const +{ + return otherConnection != nullptr && networkConnection.lock() == otherConnection; } void CConnection::disableStackSendingByID() { - -} - -void CConnection::enterLobbyConnectionMode() -{ - -} - -void CConnection::enterGameplayConnectionMode(CGameState * gs) -{ - -} - -int CConnection::write(const void * data, unsigned size) -{ - return 0; -} - -int CConnection::read(void * data, unsigned size) -{ - return 0; -} - -#if 0 - -VCMI_LIB_NAMESPACE_BEGIN - -using namespace boost; -using namespace boost::asio::ip; - -struct ConnectionBuffers -{ - boost::asio::streambuf readBuffer; - boost::asio::streambuf writeBuffer; -}; - -void CConnection::init() -{ - enableBufferedWrite = false; - enableBufferedRead = false; - connectionBuffers = std::make_unique(); - - socket->set_option(boost::asio::ip::tcp::no_delay(true)); - try - { - socket->set_option(boost::asio::socket_base::send_buffer_size(4194304)); - socket->set_option(boost::asio::socket_base::receive_buffer_size(4194304)); - } - catch (const boost::system::system_error & e) - { - logNetwork->error("error setting socket option: %s", e.what()); - } - - enableSmartPointerSerialization(); - disableStackSendingByID(); -#ifndef VCMI_ENDIAN_BIG - myEndianess = true; -#else - myEndianess = false; -#endif - connected = true; - std::string pom; - //we got connection - oser & std::string("Aiya!\n") & name & uuid & myEndianess; //identify ourselves - iser & pom & pom & contactUuid & contactEndianess; - logNetwork->info("Established connection with %s. UUID: %s", pom, contactUuid); - mutexRead = std::make_shared(); - mutexWrite = std::make_shared(); - - iser.fileVersion = SERIALIZATION_VERSION; -} - -CConnection::CConnection(const std::string & host, ui16 port, std::string Name, std::string UUID): - io_service(std::make_shared()), - iser(this), - oser(this), - name(std::move(Name)), - uuid(std::move(UUID)) -{ - int i = 0; - boost::system::error_code error = asio::error::host_not_found; - socket = std::make_shared(*io_service); - - tcp::resolver resolver(*io_service); - tcp::resolver::iterator end; - tcp::resolver::iterator pom; - tcp::resolver::iterator endpoint_iterator = resolver.resolve(tcp::resolver::query(host, std::to_string(port)), error); - if(error) - { - logNetwork->error("Problem with resolving: \n%s", error.message()); - throw std::runtime_error("Problem with resolving"); - } - pom = endpoint_iterator; - if(pom != end) - logNetwork->info("Found endpoints:"); - else - { - logNetwork->error("Critical problem: No endpoints found!"); - throw std::runtime_error("No endpoints found!"); - } - while(pom != end) - { - logNetwork->info("\t%d:%s", i, (boost::asio::ip::tcp::endpoint&)*pom); - pom++; - } - i=0; - while(endpoint_iterator != end) - { - logNetwork->info("Trying connection to %s(%d)", (boost::asio::ip::tcp::endpoint&)*endpoint_iterator, i++); - socket->connect(*endpoint_iterator, error); - if(!error) - { - init(); - return; - } - else - { - throw std::runtime_error("Failed to connect!"); - } - endpoint_iterator++; - } -} - -CConnection::CConnection(std::shared_ptr Socket, std::string Name, std::string UUID): - iser(this), - oser(this), - socket(std::move(Socket)), - name(std::move(Name)), - uuid(std::move(UUID)) -{ - init(); -} -CConnection::CConnection(const std::shared_ptr & acceptor, - const std::shared_ptr & io_service, - std::string Name, - std::string UUID): - io_service(io_service), - iser(this), - oser(this), - name(std::move(Name)), - uuid(std::move(UUID)) -{ - boost::system::error_code error = asio::error::host_not_found; - socket = std::make_shared(*io_service); - acceptor->accept(*socket,error); - if (error) - { - logNetwork->error("Error on accepting: %s", error.message()); - socket.reset(); - throw std::runtime_error("Can't establish connection :("); - } - init(); -} - -void CConnection::flushBuffers() -{ - if(!enableBufferedWrite) - return; - - if (!socket) - throw std::runtime_error("Can't write to closed socket!"); - - try - { - asio::write(*socket, connectionBuffers->writeBuffer); - } - catch(...) - { - //connection has been lost - connected = false; - throw; - } - - enableBufferedWrite = false; -} - -int CConnection::write(const void * data, unsigned size) -{ - if (!socket) - throw std::runtime_error("Can't write to closed socket!"); - - try - { - if(enableBufferedWrite) - { - std::ostream ostream(&connectionBuffers->writeBuffer); - - ostream.write(static_cast(data), size); - - return size; - } - - int ret = static_cast(asio::write(*socket, asio::const_buffers_1(asio::const_buffer(data, size)))); - return ret; - } - catch(...) - { - //connection has been lost - connected = false; - throw; - } -} - -int CConnection::read(void * data, unsigned size) -{ - try - { - if(enableBufferedRead) - { - auto available = connectionBuffers->readBuffer.size(); - - while(available < size) - { - auto bytesRead = socket->read_some(connectionBuffers->readBuffer.prepare(1024)); - connectionBuffers->readBuffer.commit(bytesRead); - available = connectionBuffers->readBuffer.size(); - } - - std::istream istream(&connectionBuffers->readBuffer); - - istream.read(static_cast(data), size); - - return size; - } - - int ret = static_cast(asio::read(*socket,asio::mutable_buffers_1(asio::mutable_buffer(data,size)))); - return ret; - } - catch(...) - { - //connection has been lost - connected = false; - throw; - } -} - -CConnection::~CConnection() -{ - close(); - - if(handler) - { - // ugly workaround to avoid self-join if last strong reference to shared_ptr that owns this class has been released in this very thread, e.g. on netpack processing - if (boost::this_thread::get_id() != handler->get_id()) - handler->join(); - else - handler->detach(); - } -} - -template -CConnection & CConnection::operator&(const T &t) { -// throw std::exception(); -//XXX this is temporaly ? solution to fix gcc (4.3.3, other?) compilation -// problem for more details contact t0@czlug.icis.pcz.pl or impono@gmail.com -// do not remove this exception it shoudnt be called - return *this; -} - -void CConnection::close() -{ - if(socket) - { - try - { - socket->shutdown(boost::asio::ip::tcp::socket::shutdown_receive); - } - catch (const boost::system::system_error & e) - { - logNetwork->error("error closing socket: %s", e.what()); - } - - socket->close(); - socket.reset(); - } -} - -bool CConnection::isOpen() const -{ - return socket && connected; -} - -void CConnection::reportState(vstd::CLoggerBase * out) -{ - out->debug("CConnection"); - if(socket && socket->is_open()) - { - out->debug("\tWe have an open and valid socket"); - out->debug("\t %d bytes awaiting", socket->available()); - } -} - -CPack * CConnection::retrievePack() -{ - enableBufferedRead = true; - - CPack * pack = nullptr; - boost::unique_lock lock(*mutexRead); - iser & pack; - logNetwork->trace("Received CPack of type %s", (pack ? typeid(*pack).name() : "nullptr")); - if(pack == nullptr) - logNetwork->error("Received a nullptr CPack! You should check whether client and server ABI matches."); - - enableBufferedRead = false; - - return pack; -} - -void CConnection::sendPack(const CPack * pack) -{ - boost::unique_lock lock(*mutexWrite); - logNetwork->trace("Sending a pack of type %s", typeid(*pack).name()); - - enableBufferedWrite = true; - - oser & pack; - - flushBuffers(); -} - -void CConnection::disableStackSendingByID() -{ - CSerializer::sendStackInstanceByIds = false; + packReader->sendStackInstanceByIds = false; + packWriter->sendStackInstanceByIds = false; } void CConnection::enableStackSendingByID() { - CSerializer::sendStackInstanceByIds = true; -} - -void CConnection::disableSmartPointerSerialization() -{ - iser.smartPointerSerialization = oser.smartPointerSerialization = false; -} - -void CConnection::enableSmartPointerSerialization() -{ - iser.smartPointerSerialization = oser.smartPointerSerialization = true; + packReader->sendStackInstanceByIds = true; + packWriter->sendStackInstanceByIds = true; } void CConnection::enterLobbyConnectionMode() { - iser.loadedPointers.clear(); - oser.savedPointers.clear(); + deserializer->loadedPointers.clear(); + serializer->savedPointers.clear(); disableSmartVectorMemberSerialization(); disableSmartPointerSerialization(); + disableStackSendingByID(); } void CConnection::enterGameplayConnectionMode(CGameState * gs) { enableStackSendingByID(); disableSmartPointerSerialization(); - addStdVecItems(gs); + + packReader->addStdVecItems(gs); + packWriter->addStdVecItems(gs); +} + +void CConnection::disableSmartPointerSerialization() +{ + deserializer->smartPointerSerialization = false; + serializer->smartPointerSerialization = false; +} + +void CConnection::enableSmartPointerSerialization() +{ + deserializer->smartPointerSerialization = true; + serializer->smartPointerSerialization = true; } void CConnection::disableSmartVectorMemberSerialization() { - CSerializer::smartVectorMembersSerialization = false; + packReader->smartVectorMembersSerialization = false; + packWriter->smartVectorMembersSerialization = false; } void CConnection::enableSmartVectorMemberSerializatoin() { - CSerializer::smartVectorMembersSerialization = true; -} - -std::string CConnection::toString() const -{ - boost::format fmt("Connection with %s (ID: %d UUID: %s)"); - fmt % name % connectionID % uuid; - return fmt.str(); + packReader->smartVectorMembersSerialization = true; + packWriter->smartVectorMembersSerialization = true; } VCMI_LIB_NAMESPACE_END - -#endif diff --git a/lib/serializer/Connection.h b/lib/serializer/Connection.h index 5dc43b8f6..7d5f307b7 100644 --- a/lib/serializer/Connection.h +++ b/lib/serializer/Connection.h @@ -9,84 +9,49 @@ */ #pragma once -#include "CSerializer.h" - VCMI_LIB_NAMESPACE_BEGIN class BinaryDeserializer; class BinarySerializer; struct CPack; -struct ConnectionBuffers; class NetworkConnection; +class ConnectionPackReader; +class ConnectionPackWriter; +class CGameState; -/// Main class for network communication -/// Allows establishing connection and bidirectional read-write -class DLL_LINKAGE CConnection : public IBinaryReader, public IBinaryWriter, public std::enable_shared_from_this +/// Wrapper class for game connection +/// Handles serialization and deserialization of data received from network +class DLL_LINKAGE CConnection : boost::noncopyable { /// Non-owning pointer to underlying connection std::weak_ptr networkConnection; -// void init(); -// void reportState(vstd::CLoggerBase * out) override; -// - int write(const void * data, unsigned size) override; - int read(void * data, unsigned size) override; -// void flushBuffers(); -// -// bool enableBufferedWrite; -// bool enableBufferedRead; -// std::unique_ptr connectionBuffers; -// - std::unique_ptr iser; - std::unique_ptr oser; -// -// std::string contactUuid; -// std::string name; //who uses this connection + std::unique_ptr packReader; + std::unique_ptr packWriter; + std::unique_ptr deserializer; + std::unique_ptr serializer; + + void disableStackSendingByID(); + void enableStackSendingByID(); + void disableSmartPointerSerialization(); + void enableSmartPointerSerialization(); + void disableSmartVectorMemberSerialization(); + void enableSmartVectorMemberSerializatoin(); public: + bool isMyConnection(const std::shared_ptr & otherConnection) const; + std::string uuid; int connectionID; CConnection(std::weak_ptr networkConnection); -// CConnection(const std::string & host, ui16 port, std::string Name, std::string UUID); -// CConnection(const std::shared_ptr & acceptor, const std::shared_ptr & Io_service, std::string Name, std::string UUID); -// CConnection(std::shared_ptr Socket, std::string Name, std::string UUID); //use immediately after accepting connection into socket -// virtual ~CConnection(); + ~CConnection(); -// void close(); -// bool isOpen() const; -// -// CPack * retrievePack(); void sendPack(const CPack * pack); - CPack * retrievePack(const std::vector & data); -// std::vector serializePack(const CPack * pack); -// - void disableStackSendingByID(); -// void enableStackSendingByID(); -// void disableSmartPointerSerialization(); -// void enableSmartPointerSerialization(); -// void disableSmartVectorMemberSerialization(); -// void enableSmartVectorMemberSerializatoin(); -// + void enterLobbyConnectionMode(); void enterGameplayConnectionMode(CGameState * gs); -// -// std::string toString() const; -// -// template -// CConnection & operator>>(T &t) -// { -// iser & t; -// return * this; -// } -// -// template -// CConnection & operator<<(const T &t) -// { -// oser & t; -// return * this; -// } }; VCMI_LIB_NAMESPACE_END diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index c88941625..3a0381af8 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -177,7 +177,10 @@ void CVCMIServer::onNewConnection(const std::shared_ptr & con establishOutgoingConnection(); if(state == EServerState::LOBBY) + { activeConnections.push_back(std::make_shared(connection));//, SERVER_NAME, uuid);) + activeConnections.back()->enterLobbyConnectionMode(); + } // TODO: else: deny connection // TODO: else: try to reconnect / send state to reconnected client } @@ -193,26 +196,16 @@ void CVCMIServer::onPacketReceived(const std::shared_ptr & co //FIXME: delete pack? } -void CVCMIServer::onPacketReceived(const std::vector & message) -{ - //TODO: handle pack received from lobby -} - void CVCMIServer::onConnectionFailed(const std::string & errorMessage) { //TODO: handle failure to connect to lobby } -void CVCMIServer::onConnectionEstablished() +void CVCMIServer::onConnectionEstablished(const std::shared_ptr &) { //TODO: handle connection to lobby - login? } -void CVCMIServer::onDisconnected() -{ - //TODO: handle disconnection from lobby -} - void CVCMIServer::setState(EServerState value) { state.store(value); @@ -225,9 +218,13 @@ EServerState CVCMIServer::getState() const std::shared_ptr CVCMIServer::findConnection(const std::shared_ptr & netConnection) { - //TODO - assert(0); - return nullptr; + for (auto const & gameConnection : activeConnections) + { + if (gameConnection->isMyConnection(netConnection)) + return gameConnection; + } + + throw std::runtime_error("Unknown connection received in CVCMIServer::findConnection"); } void CVCMIServer::run() @@ -294,10 +291,8 @@ void CVCMIServer::prepareToRestart() } for(auto c : activeConnections) - { c->enterLobbyConnectionMode(); - c->disableStackSendingByID(); - } + boost::unique_lock queueLock(mx); gh = nullptr; } @@ -417,8 +412,8 @@ void CVCMIServer::announcePack(std::unique_ptr pack) { // FIXME: we need to avoid sending something to client that not yet get answer for LobbyClientConnected // Until UUID set we only pass LobbyClientConnected to this client - if(c->uuid == uuid && !dynamic_cast(pack.get())) - continue; + //if(c->uuid == uuid && !dynamic_cast(pack.get())) + // continue; c->sendPack(pack.get()); } diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index d83c3edf0..54702cecc 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -70,16 +70,12 @@ private: std::unique_ptr remoteConnectionsThread; std::atomic state; - // INetworkServerListener impl + // INetworkListener impl void onDisconnected(const std::shared_ptr & connection) override; void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; void onNewConnection(const std::shared_ptr &) override; - - // INetworkClientListener impl - void onPacketReceived(const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; - void onConnectionEstablished() override; - void onDisconnected() override; + void onConnectionEstablished(const std::shared_ptr &) override; void establishOutgoingConnection(); diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index 254d20b22..90f0b3dc0 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -210,10 +210,7 @@ void ApplyOnServerNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack) void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack) { for(auto & c : srv.activeConnections) - { c->enterLobbyConnectionMode(); - c->disableStackSendingByID(); - } } void ClientPermissionsCheckerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) From 8ea69e457ad55e39dceebc653721cbad7023d280 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 25 Dec 2023 22:26:59 +0200 Subject: [PATCH 09/98] Simplified applying of lobby packs --- client/CMT.cpp | 1 - client/CServerHandler.cpp | 53 +++++++++++++++------------------------ client/CServerHandler.h | 14 +++-------- 3 files changed, 23 insertions(+), 45 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index 4e47b70f6..fedf5ddc9 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -451,7 +451,6 @@ static void mainLoop() while(1) //main SDL events loop { GH.input().fetchEvents(); - CSH->applyPacksOnLobbyScreen(); GH.renderFrame(); } } diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index d41fe02ec..7c8f9b865 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -76,8 +76,8 @@ extern std::atomic_bool androidTestServerReadyFlag; class CBaseForLobbyApply { public: - virtual bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const = 0; - virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const = 0; + virtual bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const = 0; + virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const = 0; virtual ~CBaseForLobbyApply(){}; template static CBaseForLobbyApply * getApplier(const U * t = nullptr) { @@ -88,40 +88,40 @@ public: template class CApplyOnLobby : public CBaseForLobbyApply { public: - bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override + bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override { boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); - T * ptr = static_cast(pack); + T & ptr = static_cast(pack); ApplyOnLobbyHandlerNetPackVisitor visitor(*handler); logNetwork->trace("\tImmediately apply on lobby: %s", typeid(ptr).name()); - ptr->visit(visitor); + ptr.visit(visitor); return visitor.getResult(); } - void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const override + void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override { - T * ptr = static_cast(pack); + T & ptr = static_cast(pack); ApplyOnLobbyScreenNetPackVisitor visitor(*handler, lobby); logNetwork->trace("\tApply on lobby from queue: %s", typeid(ptr).name()); - ptr->visit(visitor); + ptr.visit(visitor); } }; template<> class CApplyOnLobby: public CBaseForLobbyApply { public: - bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override + bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override { logGlobal->error("Cannot apply plain CPack!"); assert(0); return false; } - void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const override + void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override { logGlobal->error("Cannot apply plain CPack!"); assert(0); @@ -139,7 +139,6 @@ CServerHandler::~CServerHandler() CServerHandler::CServerHandler() : state(EClientState::NONE) - , mx(std::make_shared()) , networkClient(std::make_unique(*this)) , client(nullptr) , loadMode(0) @@ -170,7 +169,6 @@ void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std:: state = EClientState::NONE; mapToStart = nullptr; th = std::make_unique(); - packsForLobbyScreen.clear(); c.reset(); si = std::make_shared(); playerNames.clear(); @@ -321,19 +319,12 @@ void CServerHandler::onConnectionEstablished(const std::shared_ptr lock(*mx); - while(!packsForLobbyScreen.empty()) - { - boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); - CPackForLobby * pack = packsForLobbyScreen.front(); - packsForLobbyScreen.pop_front(); - CBaseForLobbyApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack)); //find the applier - apply->applyOnLobbyScreen(dynamic_cast(SEL), this, pack); - GH.windows().totalRedraw(); - delete pack; - } + boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); + CBaseForLobbyApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(&pack)); //find the applier + apply->applyOnLobbyScreen(dynamic_cast(SEL), this, pack); + GH.windows().totalRedraw(); } std::set CServerHandler::getHumanColors() @@ -956,23 +947,19 @@ void CServerHandler::onDisconnected(const std::shared_ptr &) } else { - auto lcd = new LobbyClientDisconnected(); - lcd->clientId = c->connectionID; - boost::unique_lock lock(*mx); - packsForLobbyScreen.push_back(lcd); + LobbyClientDisconnected lcd; + lcd.clientId = c->connectionID; + applyPackOnLobbyScreen(lcd); } } } void CServerHandler::visitForLobby(CPackForLobby & lobbyPack) { - if(applier->getApplier(CTypeList::getInstance().getTypeID(&lobbyPack))->applyOnLobbyHandler(this, &lobbyPack)) + if(applier->getApplier(CTypeList::getInstance().getTypeID(&lobbyPack))->applyOnLobbyHandler(this, lobbyPack)) { if(!settings["session"]["headless"].Bool()) - { - boost::unique_lock lock(*mx); - packsForLobbyScreen.push_back(&lobbyPack); - } + applyPackOnLobbyScreen(lobbyPack); } } diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 1a8ddd90e..13dcef68c 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -83,21 +83,13 @@ public: /// structure to handle running server and connecting to it class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClientListener, boost::noncopyable { - std::unique_ptr networkClient; - friend class ApplyOnLobbyHandlerNetPackVisitor; - + + std::unique_ptr networkClient; std::shared_ptr> applier; - - std::shared_ptr mx; - std::list packsForLobbyScreen; //protected by mx - std::shared_ptr mapToStart; - std::vector myNames; - std::shared_ptr highScoreCalc; - std::function onConnectedCallback; void threadRunNetwork(); @@ -110,6 +102,7 @@ class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClien void onConnectionEstablished(const std::shared_ptr &) override; void onDisconnected(const std::shared_ptr &) override; + void applyPackOnLobbyScreen(CPackForLobby & pack); public: std::shared_ptr c; @@ -143,7 +136,6 @@ public: void resetStateForLobby(const StartInfo::EMode mode, const std::vector * names = nullptr); void startLocalServerAndConnect(const std::function & onConnected); void justConnectToServer(const std::string & addr, const ui16 port, const std::function & onConnected); - void applyPacksOnLobbyScreen(); // Helpers for lobby state access std::set getHumanColors(); From 5694777a96414e7b8c3a1f46f4180fa8128566d6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 25 Dec 2023 22:56:55 +0200 Subject: [PATCH 10/98] Simplified connection logic --- client/CServerHandler.cpp | 22 ++++++---------------- client/CServerHandler.h | 5 ++--- client/NetPacksLobbyClient.cpp | 5 +++++ client/mainmenu/CMainMenu.cpp | 25 ++----------------------- 4 files changed, 15 insertions(+), 42 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 7c8f9b865..00a438064 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -181,7 +181,7 @@ void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std:: myNames.push_back(settings["general"]["playerName"].String()); } -void CServerHandler::startLocalServerAndConnect(const std::function & onConnected) +void CServerHandler::startLocalServerAndConnect() { if(threadRunLocalServer) threadRunLocalServer->join(); @@ -260,12 +260,12 @@ void CServerHandler::startLocalServerAndConnect(const std::function & on th->update(); //put breakpoint here to attach to server before it does something stupid - justConnectToServer(localhostAddress, 0, onConnected); + justConnectToServer(localhostAddress, 0); logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff()); } -void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port, const std::function & onConnected) +void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port) { logNetwork->info("Establishing connection..."); state = EClientState::CONNECTING; @@ -281,10 +281,6 @@ void CServerHandler::justConnectToServer(const std::string & addr, const ui16 po serverPort->Integer() = port; } - if (onConnectedCallback) - throw std::runtime_error("Attempt to connect while there is already a pending connection!"); - - onConnectedCallback = onConnected; networkClient->start(addr.size() ? addr : getHostAddress(), port ? port : getHostPort()); } @@ -311,12 +307,6 @@ void CServerHandler::onConnectionEstablished(const std::shared_ptr(netConnection); c->enterLobbyConnectionMode(); sendClientConnecting(); - - if (onConnectedCallback) - { - onConnectedCallback(); - onConnectedCallback = {}; - } } void CServerHandler::applyPackOnLobbyScreen(CPackForLobby & pack) @@ -820,7 +810,7 @@ void CServerHandler::restoreLastSession() myNames.push_back(name.String()); resetStateForLobby(StartInfo::LOAD_GAME, &myNames); screenType = ESelectionScreen::loadGame; - justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer(), {}); + justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer()); }; auto cleanUpSession = []() @@ -850,9 +840,9 @@ void CServerHandler::debugStartTest(std::string filename, bool save) screenType = ESelectionScreen::newGame; } if(settings["session"]["donotstartserver"].Bool()) - justConnectToServer(localhostAddress, 3030, {}); + justConnectToServer(localhostAddress, 3030); else - startLocalServerAndConnect({}); + startLocalServerAndConnect(); boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 13dcef68c..e62e9701d 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -90,7 +90,6 @@ class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClien std::shared_ptr mapToStart; std::vector myNames; std::shared_ptr highScoreCalc; - std::function onConnectedCallback; void threadRunNetwork(); void threadRunServer(); @@ -134,8 +133,8 @@ public: ui16 getHostPort() const; void resetStateForLobby(const StartInfo::EMode mode, const std::vector * names = nullptr); - void startLocalServerAndConnect(const std::function & onConnected); - void justConnectToServer(const std::string & addr, const ui16 port, const std::function & onConnected); + void startLocalServerAndConnect(); + void justConnectToServer(const std::string & addr, const ui16 port); // Helpers for lobby state access std::set getHumanColors(); diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index 1bbdd448d..4b322d238 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -43,7 +43,12 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon if(handler.mapToStart) handler.setMapInfo(handler.mapToStart); else if(!settings["session"]["headless"].Bool()) + { + if (GH.windows().topWindow()) + GH.windows().popWindows(1); + GH.windows().createAndPushWindow(static_cast(handler.screenType)); + } handler.state = EClientState::LOBBY; } } diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index ce6087e92..add98c05b 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -611,31 +611,10 @@ void CSimpleJoinScreen::startConnection(const std::string & addr, ui16 port) CVCMIServer::reuseClientJNIEnv(SDL_AndroidGetJNIEnv()); #endif - auto const & onConnected = [this]() - { - // async call to prevent thread race - GH.dispatchMainThread([this](){ - // FIXME: this enum value is never set!!! - if(CSH->state == EClientState::CONNECTION_FAILED) - { - CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {}); - - textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverAddressEnter")); - GH.startTextInput(inputAddress->pos); - buttonOk->block(false); - } - - if(GH.windows().isTopWindow(this)) - { - close(); - } - }); - }; - if(addr.empty()) - CSH->startLocalServerAndConnect(onConnected); + CSH->startLocalServerAndConnect(); else - CSH->justConnectToServer(addr, port, onConnected); + CSH->justConnectToServer(addr, port); } CLoadingScreen::CLoadingScreen() From c9765a52ff9951688adc41aa9f699a546fa7c287 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 26 Dec 2023 16:29:06 +0200 Subject: [PATCH 11/98] Do not accept connections into ongoing game --- lib/network/NetworkServer.cpp | 7 ++++++- lib/network/NetworkServer.h | 1 + server/CVCMIServer.cpp | 12 ++++++------ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp index 3c95a284d..d1cfccfe1 100644 --- a/lib/network/NetworkServer.cpp +++ b/lib/network/NetworkServer.cpp @@ -16,7 +16,6 @@ VCMI_LIB_NAMESPACE_BEGIN NetworkServer::NetworkServer(INetworkServerListener & listener) :listener(listener) { - } void NetworkServer::start(uint16_t port) @@ -63,6 +62,12 @@ void NetworkServer::sendPacket(const std::shared_ptr & connec connection->sendPacket(message); } +void NetworkServer::closeConnection(const std::shared_ptr & connection) +{ + assert(connections.count(connection)); + connections.erase(connection); +} + void NetworkServer::onDisconnected(const std::shared_ptr & connection) { assert(connections.count(connection)); diff --git a/lib/network/NetworkServer.h b/lib/network/NetworkServer.h index bf3a67c52..337aa2976 100644 --- a/lib/network/NetworkServer.h +++ b/lib/network/NetworkServer.h @@ -33,6 +33,7 @@ public: explicit NetworkServer(INetworkServerListener & listener); void sendPacket(const std::shared_ptr &, const std::vector & message); + void closeConnection(const std::shared_ptr &); void start(uint16_t port); void run(std::chrono::milliseconds duration); diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 3a0381af8..081fed60b 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -178,22 +178,22 @@ void CVCMIServer::onNewConnection(const std::shared_ptr & con if(state == EServerState::LOBBY) { - activeConnections.push_back(std::make_shared(connection));//, SERVER_NAME, uuid);) + activeConnections.push_back(std::make_shared(connection)); activeConnections.back()->enterLobbyConnectionMode(); } - // TODO: else: deny connection - // TODO: else: try to reconnect / send state to reconnected client + else + { + networkServer->closeConnection(connection); + } } void CVCMIServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) { std::shared_ptr c = findConnection(connection); - CPack * pack = c->retrievePack(message); + auto pack = c->retrievePack(message); pack->c = c; CVCMIServerPackVisitor visitor(*this, this->gh); pack->visit(visitor); - - //FIXME: delete pack? } void CVCMIServer::onConnectionFailed(const std::string & errorMessage) From d6869160c5c3b46cc4d38ce98fdf30454398532d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 26 Dec 2023 18:29:13 +0200 Subject: [PATCH 12/98] Simplify networking code on server --- server/CVCMIServer.cpp | 51 +++++++++++++++++++--------------- server/CVCMIServer.h | 10 +++---- server/NetPacksLobbyServer.cpp | 20 +++++++------ 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 081fed60b..480b4ed81 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -208,12 +208,12 @@ void CVCMIServer::onConnectionEstablished(const std::shared_ptr CVCMIServer::findConnection(const std::shared_ptr & netConnection) @@ -256,6 +256,11 @@ void CVCMIServer::run() } } +void CVCMIServer::onTimer() +{ + // FIXME: move GameHandler updates here +} + void CVCMIServer::establishOutgoingConnection() { if(!cmdLineOptions.count("lobby")) @@ -456,8 +461,10 @@ bool CVCMIServer::passHost(int toConnectionId) void CVCMIServer::clientConnected(std::shared_ptr c, std::vector & names, std::string uuid, StartInfo::EMode mode) { - if(state == EServerState::LOBBY) - c->connectionID = currentClientId++; + if(state != EServerState::LOBBY) + throw std::runtime_error("CVCMIServer::clientConnected called while game is not accepting clients!"); + + c->connectionID = currentClientId++; if(hostClientId == -1) { @@ -467,27 +474,24 @@ void CVCMIServer::clientConnected(std::shared_ptr c, std::vectorinfo("Connection with client %d established. UUID: %s", c->connectionID, c->uuid); - if(state == EServerState::LOBBY) + for(auto & name : names) { - for(auto & name : names) + logNetwork->info("Client %d player: %s", c->connectionID, name); + ui8 id = currentPlayerId++; + + ClientPlayer cp; + cp.connection = c->connectionID; + cp.name = name; + playerNames.insert(std::make_pair(id, cp)); + announceTxt(boost::str(boost::format("%s (pid %d cid %d) joins the game") % name % id % c->connectionID)); + + //put new player in first slot with AI + for(auto & elem : si->playerInfos) { - logNetwork->info("Client %d player: %s", c->connectionID, name); - ui8 id = currentPlayerId++; - - ClientPlayer cp; - cp.connection = c->connectionID; - cp.name = name; - playerNames.insert(std::make_pair(id, cp)); - announceTxt(boost::str(boost::format("%s (pid %d cid %d) joins the game") % name % id % c->connectionID)); - - //put new player in first slot with AI - for(auto & elem : si->playerInfos) + if(elem.second.isControlledByAI() && !elem.second.compOnly) { - if(elem.second.isControlledByAI() && !elem.second.compOnly) - { - setPlayerConnectedId(elem.second, id); - break; - } + setPlayerConnectedId(elem.second, id); + break; } } } @@ -1086,12 +1090,13 @@ int main(int argc, const char * argv[]) loadDLLClasses(); srand((ui32)time(nullptr)); + CVCMIServer server(opts); + #ifdef SINGLE_PROCESS_APP boost::condition_variable * cond = reinterpret_cast(const_cast(argv[0])); cond->notify_one(); #endif - CVCMIServer server(opts); server.run(); #if VCMI_ANDROID_DUAL_PROCESS diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 54702cecc..a2fbb86ba 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -63,12 +63,11 @@ private: /// List of all connections that were closed (but can still reconnect later) std::vector> inactiveConnections; - std::atomic restartGameplay; // FIXME: this is just a hack + bool restartGameplay; // FIXME: this is just a hack boost::recursive_mutex mx; std::shared_ptr> applier; - std::unique_ptr remoteConnectionsThread; - std::atomic state; + EServerState state; // INetworkListener impl void onDisconnected(const std::shared_ptr & connection) override; @@ -76,13 +75,14 @@ private: void onNewConnection(const std::shared_ptr &) override; void onConnectionFailed(const std::string & errorMessage) override; void onConnectionEstablished(const std::shared_ptr &) override; + void onTimer() override; void establishOutgoingConnection(); std::shared_ptr findConnection(const std::shared_ptr &); - std::atomic currentClientId; - std::atomic currentPlayerId; + int currentClientId; + ui8 currentPlayerId; public: std::shared_ptr gh; diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index 90f0b3dc0..a3610aace 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -63,15 +63,17 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientConnected(LobbyCl // Until UUID set we only pass LobbyClientConnected to this client pack.c->uuid = pack.uuid; srv.updateAndPropagateLobbyState(); - if(srv.getState() == EServerState::GAMEPLAY) - { - //immediately start game - std::unique_ptr startGameForReconnectedPlayer(new LobbyStartGame); - startGameForReconnectedPlayer->initializedStartInfo = srv.si; - startGameForReconnectedPlayer->initializedGameState = srv.gh->gameState(); - startGameForReconnectedPlayer->clientId = pack.c->connectionID; - srv.announcePack(std::move(startGameForReconnectedPlayer)); - } + +// FIXME: what is this??? We do NOT support reconnection into ongoing game - at the very least queries and battles are NOT serialized +// if(srv.getState() == EServerState::GAMEPLAY) +// { +// //immediately start game +// std::unique_ptr startGameForReconnectedPlayer(new LobbyStartGame); +// startGameForReconnectedPlayer->initializedStartInfo = srv.si; +// startGameForReconnectedPlayer->initializedGameState = srv.gh->gameState(); +// startGameForReconnectedPlayer->clientId = pack.c->connectionID; +// srv.announcePack(std::move(startGameForReconnectedPlayer)); +// } } void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) From a50cdbda0c08c7979bf06d8bb20a1b5b1caf2bc0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 26 Dec 2023 18:30:37 +0200 Subject: [PATCH 13/98] Added timer functionality to NetworkServer Might not be related to networking strictly speaking, but useful to have functionality for network thread --- client/serverLobby/LobbyWindow.cpp | 5 +++++ client/serverLobby/LobbyWindow.h | 1 + lib/network/NetworkClient.cpp | 9 +++++++++ lib/network/NetworkClient.h | 1 + lib/network/NetworkDefines.h | 5 +++-- lib/network/NetworkListener.h | 1 + 6 files changed, 20 insertions(+), 2 deletions(-) diff --git a/client/serverLobby/LobbyWindow.cpp b/client/serverLobby/LobbyWindow.cpp index 6d2363da4..20eadd17a 100644 --- a/client/serverLobby/LobbyWindow.cpp +++ b/client/serverLobby/LobbyWindow.cpp @@ -90,6 +90,11 @@ void LobbyClient::onDisconnected(const std::shared_ptr &) CInfoWindow::showInfoDialog("Connection to game lobby was lost!", {}); } +void LobbyClient::onTimer() +{ + // no-op +} + void LobbyClient::sendMessage(const JsonNode & data) { std::string payloadString = data.toJson(true); diff --git a/client/serverLobby/LobbyWindow.h b/client/serverLobby/LobbyWindow.h index 70b221751..e53c45358 100644 --- a/client/serverLobby/LobbyWindow.h +++ b/client/serverLobby/LobbyWindow.h @@ -36,6 +36,7 @@ class LobbyClient : public INetworkClientListener void onConnectionFailed(const std::string & errorMessage) override; void onConnectionEstablished(const std::shared_ptr &) override; void onDisconnected(const std::shared_ptr &) override; + void onTimer() override; public: explicit LobbyClient(LobbyWindow * window); diff --git a/lib/network/NetworkClient.cpp b/lib/network/NetworkClient.cpp index d67069b57..97da4eabc 100644 --- a/lib/network/NetworkClient.cpp +++ b/lib/network/NetworkClient.cpp @@ -75,6 +75,15 @@ void NetworkClient::stop() io->stop(); } +void NetworkClient::setTimer(std::chrono::milliseconds duration) +{ + auto timer = std::make_shared(*io, duration); + timer->async_wait([this, timer](const boost::system::error_code& error){ + if (!error) + listener.onTimer(); + }); +} + void NetworkClient::sendPacket(const std::vector & message) { connection->sendPacket(message); diff --git a/lib/network/NetworkClient.h b/lib/network/NetworkClient.h index 2f80e9a40..ae35c7400 100644 --- a/lib/network/NetworkClient.h +++ b/lib/network/NetworkClient.h @@ -37,6 +37,7 @@ public: NetworkClient(INetworkClientListener & listener); virtual ~NetworkClient() = default; + void setTimer(std::chrono::milliseconds duration); void sendPacket(const std::vector & message); void start(const std::string & host, uint16_t port); diff --git a/lib/network/NetworkDefines.h b/lib/network/NetworkDefines.h index bfd24eced..0dce17821 100644 --- a/lib/network/NetworkDefines.h +++ b/lib/network/NetworkDefines.h @@ -14,8 +14,9 @@ VCMI_LIB_NAMESPACE_BEGIN using NetworkService = boost::asio::io_service; -using NetworkSocket = boost::asio::basic_stream_socket; -using NetworkAcceptor = boost::asio::basic_socket_acceptor; +using NetworkSocket = boost::asio::ip::tcp::socket; +using NetworkAcceptor = boost::asio::ip::tcp::acceptor; using NetworkBuffer = boost::asio::streambuf; +using NetworkTimer = boost::asio::steady_timer; VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkListener.h b/lib/network/NetworkListener.h index 6b9f3abc2..4148e6503 100644 --- a/lib/network/NetworkListener.h +++ b/lib/network/NetworkListener.h @@ -38,6 +38,7 @@ class DLL_LINKAGE INetworkClientListener : public INetworkConnectionListener { friend class NetworkClient; protected: + virtual void onTimer() = 0; virtual void onConnectionFailed(const std::string & errorMessage) = 0; virtual void onConnectionEstablished(const std::shared_ptr &) = 0; From a3639e77b1ab07da32acc5bed6670aa085c56b35 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 26 Dec 2023 18:30:57 +0200 Subject: [PATCH 14/98] Fixed handling of connection failure on client --- client/CServerHandler.cpp | 24 ++++++++++++++++++------ client/CServerHandler.h | 1 + 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 00a438064..7fef5cd1e 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -251,7 +251,7 @@ void CServerHandler::startLocalServerAndConnect() while(!androidTestServerReadyFlag.load()) { logNetwork->info("still waiting..."); - boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); + boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); } logNetwork->info("waiting for server finished..."); androidTestServerReadyFlag = false; @@ -285,6 +285,22 @@ void CServerHandler::justConnectToServer(const std::string & addr, const ui16 po } void CServerHandler::onConnectionFailed(const std::string & errorMessage) +{ + if (isServerLocal()) + { + // retry - local server might be still starting up + logNetwork->debug("\nCannot establish connection. %s Retrying...", errorMessage); + networkClient->setTimer(std::chrono::milliseconds(100)); + } + else + { + // remote server refused connection - show error message + state = EClientState::CONNECTION_FAILED; + CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {}); + } +} + +void CServerHandler::onTimer() { if(state == EClientState::CONNECTION_CANCELLED) { @@ -292,11 +308,6 @@ void CServerHandler::onConnectionFailed(const std::string & errorMessage) return; } - logNetwork->warn("\nCannot establish connection. %s Retrying in 1 second", errorMessage); - - //FIXME: replace with asio timer - boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); - //FIXME: pass parameters from initial attempt networkClient->start(getHostAddress(), getHostPort()); } @@ -1001,6 +1012,7 @@ void CServerHandler::threadRunServer() } else { + state = EClientState::CONNECTION_CANCELLED; // stop attempts to reconnect logNetwork->error("Error: server failed to close correctly or crashed!"); logNetwork->error("Check %s for more info", logName); } diff --git a/client/CServerHandler.h b/client/CServerHandler.h index e62e9701d..790a26adb 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -100,6 +100,7 @@ class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClien void onConnectionFailed(const std::string & errorMessage) override; void onConnectionEstablished(const std::shared_ptr &) override; void onDisconnected(const std::shared_ptr &) override; + void onTimer() override; void applyPackOnLobbyScreen(CPackForLobby & pack); public: From 2396c14114c05ca71a2e98b8f940fbea861072ee Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 26 Dec 2023 19:27:47 +0200 Subject: [PATCH 15/98] Fixed logic for reconnection attempts to local server. Cleanup. Added distinct fields in config for local and remote connection. Removed code for restoring last session since it does not works as intended and often triggers after crash --- client/CMT.cpp | 6 -- client/CServerHandler.cpp | 115 ++++++++++------------------------ client/CServerHandler.h | 25 ++++---- client/mainmenu/CMainMenu.cpp | 6 +- config/schemas/settings.json | 31 +++------ 5 files changed, 60 insertions(+), 123 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index fedf5ddc9..05cab7883 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -405,12 +405,6 @@ int main(int argc, char * argv[]) ESelectionScreen sscreen = session["gamemode"].Integer() == 0 ? ESelectionScreen::newGame : ESelectionScreen::loadGame; CMM->openLobby(sscreen, session["host"].Bool(), &names, ELoadMode::MULTI); } - - // Restore remote session - start game immediately - if(settings["server"]["reconnect"].Bool()) - { - CSH->restoreLastSession(); - } if(!settings["session"]["headless"].Bool()) { diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 7fef5cd1e..1d524f145 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -67,8 +67,6 @@ template class CApplyOnLobby; -const std::string CServerHandler::localhostAddress{"127.0.0.1"}; - #if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP) extern std::atomic_bool androidTestServerReadyFlag; #endif @@ -200,7 +198,7 @@ void CServerHandler::startLocalServerAndConnect() #if defined(SINGLE_PROCESS_APP) boost::condition_variable cond; - std::vector args{"--uuid=" + uuid, "--port=" + std::to_string(getHostPort())}; + std::vector args{"--uuid=" + uuid, "--port=" + std::to_string(getLocalPort())}; if(settings["session"]["lobby"].Bool() && settings["session"]["host"].Bool()) { args.push_back("--lobby=" + settings["session"]["address"].String()); @@ -260,28 +258,28 @@ void CServerHandler::startLocalServerAndConnect() th->update(); //put breakpoint here to attach to server before it does something stupid - justConnectToServer(localhostAddress, 0); + connectToServer(getLocalHostname(), getLocalPort()); logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff()); } -void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port) +void CServerHandler::connectToServer(const std::string & addr, const ui16 port) { - logNetwork->info("Establishing connection..."); + logNetwork->info("Establishing connection to %s:%d...", addr, port); state = EClientState::CONNECTING; + serverHostname = addr; + serverPort = port; - if(!addr.empty() && addr != getHostAddress()) + if (!isServerLocal()) { - Settings serverAddress = settings.write["server"]["server"]; + Settings serverAddress = settings.write["server"]["remoteHostname"]; serverAddress->String() = addr; - } - if(port && port != getHostPort()) - { - Settings serverPort = settings.write["server"]["port"]; + + Settings serverPort = settings.write["server"]["remotePort"]; serverPort->Integer() = port; } - networkClient->start(addr.size() ? addr : getHostAddress(), port ? port : getHostPort()); + networkClient->start(addr, port); } void CServerHandler::onConnectionFailed(const std::string & errorMessage) @@ -289,7 +287,7 @@ void CServerHandler::onConnectionFailed(const std::string & errorMessage) if (isServerLocal()) { // retry - local server might be still starting up - logNetwork->debug("\nCannot establish connection. %s Retrying...", errorMessage); + logNetwork->debug("\nCannot establish connection. %s. Retrying...", errorMessage); networkClient->setTimer(std::chrono::milliseconds(100)); } else @@ -308,8 +306,8 @@ void CServerHandler::onTimer() return; } - //FIXME: pass parameters from initial attempt - networkClient->start(getHostAddress(), getHostPort()); + assert(isServerLocal()); + networkClient->start(getLocalHostname(), getLocalPort()); } void CServerHandler::onConnectionEstablished(const std::shared_ptr & netConnection) @@ -367,36 +365,34 @@ bool CServerHandler::isGuest() const return !c || hostClientId != c->connectionID; } -ui16 CServerHandler::getDefaultPort() +const std::string & CServerHandler::getLocalHostname() const { - return static_cast(settings["server"]["port"].Integer()); + return settings["server"]["localHostname"].String(); } -std::string CServerHandler::getDefaultPortStr() +ui16 CServerHandler::getLocalPort() const { - return std::to_string(getDefaultPort()); + return settings["server"]["localPort"].Integer(); } -std::string CServerHandler::getHostAddress() const +const std::string & CServerHandler::getRemoteHostname() const { - if(settings["session"]["lobby"].isNull() || !settings["session"]["lobby"].Bool()) - return settings["server"]["server"].String(); - - if(settings["session"]["host"].Bool()) - return localhostAddress; - - return settings["session"]["address"].String(); + return settings["server"]["remoteHostname"].String(); } -ui16 CServerHandler::getHostPort() const +ui16 CServerHandler::getRemotePort() const { - if(settings["session"]["lobby"].isNull() || !settings["session"]["lobby"].Bool()) - return getDefaultPort(); - - if(settings["session"]["host"].Bool()) - return getDefaultPort(); - - return settings["session"]["port"].Integer(); + return settings["server"]["remotePort"].Integer(); +} + +const std::string & CServerHandler::getCurrentHostname() const +{ + return serverHostname; +} + +ui16 CServerHandler::getCurrentPort() const +{ + return serverPort; } void CServerHandler::sendClientConnecting() const @@ -667,23 +663,6 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta // After everything initialized we can accept CPackToClient netpacks c->enterGameplayConnectionMode(client->gameState()); state = EClientState::GAMEPLAY; - - //store settings to continue game - if(!isServerLocal() && isGuest()) - { - Settings saveSession = settings.write["server"]["reconnect"]; - saveSession->Bool() = true; - Settings saveUuid = settings.write["server"]["uuid"]; - saveUuid->String() = uuid; - Settings saveNames = settings.write["server"]["names"]; - saveNames->Vector().clear(); - for(auto & name : myNames) - { - JsonNode jsonName; - jsonName.String() = name; - saveNames->Vector().push_back(jsonName); - } - } } void CServerHandler::endGameplay(bool closeConnection, bool restart) @@ -714,10 +693,6 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart) if(c) c->enterLobbyConnectionMode(); - - //reset settings - Settings saveSession = settings.write["server"]["reconnect"]; - saveSession->Bool() = false; } void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared_ptr cs) @@ -812,28 +787,6 @@ ui8 CServerHandler::getLoadMode() return loadMode; } -void CServerHandler::restoreLastSession() -{ - auto loadSession = [this]() - { - uuid = settings["server"]["uuid"].String(); - for(auto & name : settings["server"]["names"].Vector()) - myNames.push_back(name.String()); - resetStateForLobby(StartInfo::LOAD_GAME, &myNames); - screenType = ESelectionScreen::loadGame; - justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer()); - }; - - auto cleanUpSession = []() - { - //reset settings - Settings saveSession = settings.write["server"]["reconnect"]; - saveSession->Bool() = false; - }; - - CInfoWindow::showYesNoDialog(VLC->generaltexth->translate("vcmi.server.confirmReconnect"), {}, loadSession, cleanUpSession); -} - void CServerHandler::debugStartTest(std::string filename, bool save) { logGlobal->info("Starting debug test with file: %s", filename); @@ -851,7 +804,7 @@ void CServerHandler::debugStartTest(std::string filename, bool save) screenType = ESelectionScreen::newGame; } if(settings["session"]["donotstartserver"].Bool()) - justConnectToServer(localhostAddress, 3030); + connectToServer(getLocalHostname(), getLocalPort()); else startLocalServerAndConnect(); @@ -975,7 +928,7 @@ void CServerHandler::threadRunServer() setThreadName("runServer"); const std::string logName = (VCMIDirs::get().userLogsPath() / "server_log.txt").string(); std::string comm = VCMIDirs::get().serverPath().string() - + " --port=" + std::to_string(getHostPort()) + + " --port=" + std::to_string(getLocalPort()) + " --run-by-client" + " --uuid=" + uuid; if(settings["session"]["lobby"].Bool() && settings["session"]["host"].Bool()) diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 790a26adb..7d5392967 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -103,6 +103,12 @@ class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClien void onTimer() override; void applyPackOnLobbyScreen(CPackForLobby & pack); + + std::string serverHostname; + ui16 serverPort; + + bool isServerLocal() const; + public: std::shared_ptr c; @@ -125,17 +131,12 @@ public: CondSh campaignServerRestartLock; - static const std::string localhostAddress; - CServerHandler(); ~CServerHandler(); - std::string getHostAddress() const; - ui16 getHostPort() const; - void resetStateForLobby(const StartInfo::EMode mode, const std::vector * names = nullptr); void startLocalServerAndConnect(); - void justConnectToServer(const std::string & addr, const ui16 port); + void connectToServer(const std::string & addr, const ui16 port); // Helpers for lobby state access std::set getHumanColors(); @@ -143,12 +144,16 @@ public: bool isMyColor(PlayerColor color) const; ui8 myFirstId() const; // Used by chat only! - bool isServerLocal() const; bool isHost() const; bool isGuest() const; - static ui16 getDefaultPort(); - static std::string getDefaultPortStr(); + const std::string & getCurrentHostname() const; + const std::string & getLocalHostname() const; + const std::string & getRemoteHostname() const; + + ui16 getCurrentPort() const; + ui16 getLocalPort() const; + ui16 getRemotePort() const; // Lobby server API for UI void sendClientConnecting() const override; @@ -182,8 +187,6 @@ public: int howManyPlayerInterfaces(); ui8 getLoadMode(); - void restoreLastSession(); - void visitForLobby(CPackForLobby & lobbyPack); void visitForClient(CPackForClient & clientPack); }; diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index add98c05b..77668304c 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -568,8 +568,8 @@ CSimpleJoinScreen::CSimpleJoinScreen(bool host) inputPort->filters += std::bind(&CTextInput::numberFilter, _1, _2, 0, 65535); inputAddress->giveFocus(); } - inputAddress->setText(host ? CServerHandler::localhostAddress : CSH->getHostAddress(), true); - inputPort->setText(std::to_string(CSH->getHostPort()), true); + inputAddress->setText(host ? CSH->getLocalHostname() : CSH->getRemoteHostname(), true); + inputPort->setText(std::to_string(host ? CSH->getLocalPort() : CSH->getRemotePort()), true); buttonCancel = std::make_shared(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CSimpleJoinScreen::leaveScreen, this), EShortcut::GLOBAL_CANCEL); statusBar = CGStatusBar::create(std::make_shared(background->getSurface(), Rect(7, 186, 218, 18), 7, 186)); @@ -614,7 +614,7 @@ void CSimpleJoinScreen::startConnection(const std::string & addr, ui16 port) if(addr.empty()) CSH->startLocalServerAndConnect(); else - CSH->justConnectToServer(addr, port); + CSH->connectToServer(addr, port); } CLoadingScreen::CLoadingScreen() diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 48c3e1211..8f5595c42 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -357,19 +357,23 @@ "type" : "object", "additionalProperties" : false, "default" : {}, - "required" : [ "server", "port", "localInformation", "playerAI", "alliedAI", "friendlyAI", "neutralAI", "enemyAI", "reconnect", "uuid", "names" ], + "required" : [ "localHostname", "localPort", "remoteHostname", "remotePort", "playerAI", "alliedAI", "friendlyAI", "neutralAI", "enemyAI" ], "properties" : { - "server" : { + "localHostname" : { "type" : "string", "default" : "127.0.0.1" }, - "port" : { + "localPort" : { "type" : "number", "default" : 3030 }, - "localInformation" : { + "remoteHostname" : { + "type" : "string", + "default" : "" + }, + "remotePort" : { "type" : "number", - "default" : 2 + "default" : 3030 }, "playerAI" : { "type" : "string", @@ -390,23 +394,6 @@ "enemyAI" : { "type" : "string", "default" : "BattleAI" - }, - "reconnect" : { - "type" : "boolean", - "default" : false - }, - "uuid" : { - "type" : "string", - "default" : "" - }, - "names" : { - "type" : "array", - "default" : [], - "items" : - { - "type" : "string", - "default" : "" - } } } }, From 01967070838e244b9ed395e46e5589a6b6bc7b78 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 26 Dec 2023 19:50:44 +0200 Subject: [PATCH 16/98] Fix UUID initialization on client --- client/CServerHandler.cpp | 7 ++----- client/CServerHandler.h | 2 +- client/NetPacksLobbyClient.cpp | 3 +-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 1d524f145..55a3c1bc5 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -138,18 +138,14 @@ CServerHandler::~CServerHandler() CServerHandler::CServerHandler() : state(EClientState::NONE) , networkClient(std::make_unique(*this)) + , applier(std::make_unique>()) , client(nullptr) , loadMode(0) , campaignStateToSend(nullptr) , campaignServerRestartLock(false) { uuid = boost::uuids::to_string(boost::uuids::random_generator()()); - //read from file to restore last session - if(!settings["server"]["uuid"].isNull() && !settings["server"]["uuid"].String().empty()) - uuid = settings["server"]["uuid"].String(); - applier = std::make_shared>(); registerTypesLobbyPacks(*applier); - threadNetwork = std::make_unique(&CServerHandler::threadRunNetwork, this); } @@ -314,6 +310,7 @@ void CServerHandler::onConnectionEstablished(const std::shared_ptrinfo("Connection established"); c = std::make_shared(netConnection); + c->uuid = uuid; c->enterLobbyConnectionMode(); sendClientConnecting(); } diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 7d5392967..fd3dd1145 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -86,7 +86,7 @@ class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClien friend class ApplyOnLobbyHandlerNetPackVisitor; std::unique_ptr networkClient; - std::shared_ptr> applier; + std::unique_ptr> applier; std::shared_ptr mapToStart; std::vector myNames; std::shared_ptr highScoreCalc; diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index 4b322d238..e28fb4682 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -36,8 +36,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon result = false; // Check if it's LobbyClientConnected for our client - // TODO: restore - //if(pack.uuid == handler.c->uuid) + if(pack.uuid == handler.c->uuid) { handler.c->connectionID = pack.clientId; if(handler.mapToStart) From aa7ecea683de69fe0066cff839f51a0a175254bd Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 26 Dec 2023 20:54:32 +0200 Subject: [PATCH 17/98] Switch turn timers handling to boost asio timer --- lib/network/NetworkListener.h | 3 ++- lib/network/NetworkServer.cpp | 10 +++++++++ lib/network/NetworkServer.h | 1 + lobby/LobbyServer.cpp | 5 +++++ lobby/LobbyServer.h | 1 + server/CVCMIServer.cpp | 40 ++++++++++++++++++----------------- server/CVCMIServer.h | 3 +++ 7 files changed, 43 insertions(+), 20 deletions(-) diff --git a/lib/network/NetworkListener.h b/lib/network/NetworkListener.h index 4148e6503..c9a5e6202 100644 --- a/lib/network/NetworkListener.h +++ b/lib/network/NetworkListener.h @@ -30,6 +30,7 @@ class DLL_LINKAGE INetworkServerListener : public INetworkConnectionListener friend class NetworkServer; protected: virtual void onNewConnection(const std::shared_ptr &) = 0; + virtual void onTimer() = 0; ~INetworkServerListener() = default; }; @@ -38,9 +39,9 @@ class DLL_LINKAGE INetworkClientListener : public INetworkConnectionListener { friend class NetworkClient; protected: - virtual void onTimer() = 0; virtual void onConnectionFailed(const std::string & errorMessage) = 0; virtual void onConnectionEstablished(const std::shared_ptr &) = 0; + virtual void onTimer() = 0; ~INetworkClientListener() = default; }; diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp index d1cfccfe1..e96cae41e 100644 --- a/lib/network/NetworkServer.cpp +++ b/lib/network/NetworkServer.cpp @@ -80,4 +80,14 @@ void NetworkServer::onPacketReceived(const std::shared_ptr & listener.onPacketReceived(connection, message); } +void NetworkServer::setTimer(std::chrono::milliseconds duration) +{ + auto timer = std::make_shared(*io, duration); + timer->async_wait([this, timer](const boost::system::error_code& error){ + if (!error) + listener.onTimer(); + }); +} + + VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkServer.h b/lib/network/NetworkServer.h index 337aa2976..363032ee1 100644 --- a/lib/network/NetworkServer.h +++ b/lib/network/NetworkServer.h @@ -34,6 +34,7 @@ public: void sendPacket(const std::shared_ptr &, const std::vector & message); void closeConnection(const std::shared_ptr &); + void setTimer(std::chrono::milliseconds duration); void start(uint16_t port); void run(std::chrono::milliseconds duration); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index a9eabb3ef..3b09ef89d 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -107,6 +107,11 @@ void LobbyServer::sendMessage(const std::shared_ptr & target, networkServer->sendPacket(target, payloadBuffer); } +void LobbyServer::onTimer() +{ + // no-op +} + void LobbyServer::onNewConnection(const std::shared_ptr & connection) { } diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index 8ebc0ba55..82ac19c81 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -56,6 +56,7 @@ class LobbyServer : public INetworkServerListener void onNewConnection(const std::shared_ptr &) override; void onDisconnected(const std::shared_ptr &) override; void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; + void onTimer() override; void sendMessage(const std::shared_ptr & target, const JsonNode & json); public: diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 480b4ed81..d6240f1e8 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -236,29 +236,29 @@ void CVCMIServer::run() vmHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady"); } #endif - - static const int serverUpdateIntervalMilliseconds = 50; - auto clockInitial = std::chrono::steady_clock::now(); - int64_t msPassedLast = 0; - - while(state != EServerState::SHUTDOWN) - { - networkServer->run(std::chrono::milliseconds(serverUpdateIntervalMilliseconds)); - - const auto clockNow = std::chrono::steady_clock::now(); - const auto clockPassed = clockNow - clockInitial; - const int64_t msPassedNow = std::chrono::duration_cast(clockPassed).count(); - const int64_t msDelta = msPassedNow - msPassedLast; - msPassedLast = msPassedNow; - - if (state == EServerState::GAMEPLAY) - gh->tick(msDelta); - } + networkServer->run(); } void CVCMIServer::onTimer() { - // FIXME: move GameHandler updates here + if (state != EServerState::GAMEPLAY) + return; + + static const auto serverUpdateInterval = std::chrono::milliseconds(100); + + auto timeNow = std::chrono::steady_clock::now(); + auto timePassedBefore = lastTimerUpdateTime - gameplayStartTime; + auto timePassedNow = timeNow - gameplayStartTime; + + lastTimerUpdateTime = timeNow; + + auto msPassedBefore = std::chrono::duration_cast(timePassedBefore); + auto msPassedNow = std::chrono::duration_cast(timePassedNow); + auto msDelta = msPassedNow - msPassedBefore; + + if (msDelta.count()) + gh->tick(msDelta.count()); + networkServer->setTimer(serverUpdateInterval); } void CVCMIServer::establishOutgoingConnection() @@ -372,6 +372,8 @@ void CVCMIServer::startGameImmediately() gh->start(si->mode == StartInfo::LOAD_GAME); state = EServerState::GAMEPLAY; + lastTimerUpdateTime = gameplayStartTime = std::chrono::steady_clock::now(); + onTimer(); } void CVCMIServer::onDisconnected(const std::shared_ptr & connection) diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index a2fbb86ba..142b6bdb2 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -55,6 +55,9 @@ class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INet /// Outgoing connection established by this server to game lobby for proxy mode (only in lobby game) std::unique_ptr outgoingConnection; + std::chrono::steady_clock::time_point gameplayStartTime; + std::chrono::steady_clock::time_point lastTimerUpdateTime; + public: /// List of all active connections std::vector> activeConnections; From 78b7d9e726831671bbb58c42e673a05d3acf04da Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 26 Dec 2023 21:13:46 +0200 Subject: [PATCH 18/98] Replaced open port check with server crash check --- client/CServerHandler.cpp | 18 ++++++++---------- lib/network/NetworkClient.cpp | 17 ----------------- lib/network/NetworkClient.h | 4 ---- 3 files changed, 8 insertions(+), 31 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 55a3c1bc5..0719af7fa 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -181,17 +181,7 @@ void CServerHandler::startLocalServerAndConnect() threadRunLocalServer->join(); th->update(); - - auto errorMsg = CGI->generaltexth->translate("vcmi.server.errors.existingProcess"); -// TODO: restore -// if (!checkNetworkPortIsFree(localhostAddress, getDefaultPort())) -// { -// logNetwork->error("Port is busy, check if another instance of vcmiserver is working"); -// CInfoWindow::showInfoDialog(errorMsg, {}); -// return; -// } - #if defined(SINGLE_PROCESS_APP) boost::condition_variable cond; std::vector args{"--uuid=" + uuid, "--port=" + std::to_string(getLocalPort())}; @@ -962,6 +952,14 @@ void CServerHandler::threadRunServer() } else { + if (state != EClientState::DISCONNECTING) + { + if (state == EClientState::CONNECTING) + CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.server.errors.existingProcess"), {}); + else + CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.server.errors.serverCrashed"), {}); + } + state = EClientState::CONNECTION_CANCELLED; // stop attempts to reconnect logNetwork->error("Error: server failed to close correctly or crashed!"); logNetwork->error("Check %s for more info", logName); diff --git a/lib/network/NetworkClient.cpp b/lib/network/NetworkClient.cpp index 97da4eabc..44624e0d9 100644 --- a/lib/network/NetworkClient.cpp +++ b/lib/network/NetworkClient.cpp @@ -13,23 +13,6 @@ VCMI_LIB_NAMESPACE_BEGIN -DLL_LINKAGE bool checkNetworkPortIsFree(const std::string & host, uint16_t port) -{ - boost::asio::io_service io; - NetworkAcceptor acceptor(io); - - boost::system::error_code ec; - acceptor.open(boost::asio::ip::tcp::v4(), ec); - if (ec) - return false; - - acceptor.bind(boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port), ec); - if (ec) - return false; - - return true; -} - NetworkClient::NetworkClient(INetworkClientListener & listener) : io(new NetworkService) , socket(new NetworkSocket(*io)) diff --git a/lib/network/NetworkClient.h b/lib/network/NetworkClient.h index ae35c7400..ef45af7c6 100644 --- a/lib/network/NetworkClient.h +++ b/lib/network/NetworkClient.h @@ -14,10 +14,6 @@ VCMI_LIB_NAMESPACE_BEGIN -/// Function that attempts to open specified port on local system to determine whether port is in use -/// Returns: true if port is free and can be used to receive connections -DLL_LINKAGE bool checkNetworkPortIsFree(const std::string & host, uint16_t port); - class NetworkConnection; class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionListener From 11c2708d83dbbc0b247877234085704d34049c76 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 27 Dec 2023 14:24:31 +0200 Subject: [PATCH 19/98] Simplify server networking code, disable player takeover by AI for now --- server/CVCMIServer.cpp | 89 +++++++++++++++++++----------------------- server/CVCMIServer.h | 3 -- 2 files changed, 41 insertions(+), 51 deletions(-) diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index d6240f1e8..2f22b4c80 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -381,8 +381,6 @@ void CVCMIServer::onDisconnected(const std::shared_ptr & conn logNetwork->error("Network error receiving a pack. Connection has been closed"); std::shared_ptr c = findConnection(connection); - - inactiveConnections.push_back(c); vstd::erase(activeConnections, c); if(activeConnections.empty() || hostClientId == c->connectionID) @@ -391,19 +389,12 @@ void CVCMIServer::onDisconnected(const std::shared_ptr & conn if(gh && state == EServerState::GAMEPLAY) { gh->handleClientDisconnection(c); + + auto lcd = std::make_unique(); + lcd->c = c; + lcd->clientId = c->connectionID; + handleReceivedPack(std::move(lcd)); } - - boost::unique_lock queueLock(mx); - -// if(c->connected) -// { -// auto lcd = std::make_unique(); -// lcd->c = c; -// lcd->clientId = c->connectionID; -// handleReceivedPack(std::move(lcd)); -// } -// -// logNetwork->info("Thread listening for %s ended", c->toString()); } void CVCMIServer::handleReceivedPack(std::unique_ptr pack) @@ -508,41 +499,43 @@ void CVCMIServer::clientDisconnected(std::shared_ptr c) state = EServerState::SHUTDOWN; return; } - - PlayerReinitInterface startAiPack; - startAiPack.playerConnectionId = PlayerSettings::PLAYER_AI; - - for(auto it = playerNames.begin(); it != playerNames.end();) - { - if(it->second.connection != c->connectionID) - { - ++it; - continue; - } - int id = it->first; - std::string playerLeftMsgText = boost::str(boost::format("%s (pid %d cid %d) left the game") % id % playerNames[id].name % c->connectionID); - announceTxt(playerLeftMsgText); //send lobby text, it will be ignored for non-lobby clients - auto * playerSettings = si->getPlayersSettings(id); - if(!playerSettings) - { - ++it; - continue; - } - - it = playerNames.erase(it); - setPlayerConnectedId(*playerSettings, PlayerSettings::PLAYER_AI); - - if(gh && si && state == EServerState::GAMEPLAY) - { - gh->playerMessages->broadcastMessage(playerSettings->color, playerLeftMsgText); - // gh->connections[playerSettings->color].insert(hostClient); - startAiPack.players.push_back(playerSettings->color); - } - } - - if(!startAiPack.players.empty()) - gh->sendAndApply(&startAiPack); + // TODO: close network connection + +// PlayerReinitInterface startAiPack; +// startAiPack.playerConnectionId = PlayerSettings::PLAYER_AI; +// +// for(auto it = playerNames.begin(); it != playerNames.end();) +// { +// if(it->second.connection != c->connectionID) +// { +// ++it; +// continue; +// } +// +// int id = it->first; +// std::string playerLeftMsgText = boost::str(boost::format("%s (pid %d cid %d) left the game") % id % playerNames[id].name % c->connectionID); +// announceTxt(playerLeftMsgText); //send lobby text, it will be ignored for non-lobby clients +// auto * playerSettings = si->getPlayersSettings(id); +// if(!playerSettings) +// { +// ++it; +// continue; +// } +// +// it = playerNames.erase(it); +// setPlayerConnectedId(*playerSettings, PlayerSettings::PLAYER_AI); +// +// if(gh && si && state == EServerState::GAMEPLAY) +// { +// gh->playerMessages->broadcastMessage(playerSettings->color, playerLeftMsgText); +// // gh->connections[playerSettings->color].insert(hostClient); +// startAiPack.players.push_back(playerSettings->color); +// } +// } +// +// if(!startAiPack.players.empty()) +// gh->sendAndApply(&startAiPack); } void CVCMIServer::reconnectPlayer(int connId) diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 142b6bdb2..030e44e81 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -63,9 +63,6 @@ public: std::vector> activeConnections; private: - /// List of all connections that were closed (but can still reconnect later) - std::vector> inactiveConnections; - bool restartGameplay; // FIXME: this is just a hack boost::recursive_mutex mx; From ee797cb2457187b71c95dee98b5495dead9aa7ac Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 27 Dec 2023 14:24:49 +0200 Subject: [PATCH 20/98] Split method into smaller segments --- lobby/LobbyServer.cpp | 78 +++++++++++++++++++++++-------------------- lobby/LobbyServer.h | 3 ++ 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 3b09ef89d..29cdce371 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -128,48 +128,54 @@ void LobbyServer::onPacketReceived(const std::shared_ptr & co JsonNode json(payloadBegin, message.size()); if (json["type"].String() == "sendChatMessage") - { - if (activeAccounts.count(connection) == 0) - return; // unauthenticated - - std::string senderName = activeAccounts[connection].accountName; - std::string messageText = json["messageText"].String(); - - database->insertChatMessage(senderName, messageText); - - JsonNode reply; - reply["type"].String() = "chatMessage"; - reply["messageText"].String() = messageText; - reply["senderName"].String() = senderName; - - for (auto const & connection : activeAccounts) - sendMessage(connection.first, reply); - } + return receiveSendChatMessage(connection, json); if (json["type"].String() == "authentication") + return receiveAuthentication(connection, json); +} + +void LobbyServer::receiveSendChatMessage(const std::shared_ptr & connection, const JsonNode & json) +{ + if (activeAccounts.count(connection) == 0) + return; // unauthenticated + + std::string senderName = activeAccounts[connection].accountName; + std::string messageText = json["messageText"].String(); + + database->insertChatMessage(senderName, messageText); + + JsonNode reply; + reply["type"].String() = "chatMessage"; + reply["messageText"].String() = messageText; + reply["senderName"].String() = senderName; + + for (auto const & connection : activeAccounts) + sendMessage(connection.first, reply); +} + +void LobbyServer::receiveAuthentication(const std::shared_ptr & connection, const JsonNode & json) +{ + std::string accountName = json["accountName"].String(); + + activeAccounts[connection].accountName = accountName; + + auto history = database->getRecentMessageHistory(); + + JsonNode reply; + reply["type"].String() = "chatHistory"; + + for (auto const & message : boost::adaptors::reverse(history)) { - std::string accountName = json["accountName"].String(); + JsonNode jsonEntry; - activeAccounts[connection].accountName = accountName; + jsonEntry["messageText"].String() = message.messageText; + jsonEntry["senderName"].String() = message.sender; + jsonEntry["ageSeconds"].Integer() = message.messageAgeSeconds; - auto history = database->getRecentMessageHistory(); - - JsonNode json; - json["type"].String() = "chatHistory"; - - for (auto const & message : boost::adaptors::reverse(history)) - { - JsonNode jsonEntry; - - jsonEntry["messageText"].String() = message.messageText; - jsonEntry["senderName"].String() = message.sender; - jsonEntry["ageSeconds"].Integer() = message.messageAgeSeconds; - - json["messages"].Vector().push_back(jsonEntry); - } - - sendMessage(connection, json); + reply["messages"].Vector().push_back(jsonEntry); } + + sendMessage(connection, reply); } LobbyServer::LobbyServer() diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index 82ac19c81..da334ee7f 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -59,6 +59,9 @@ class LobbyServer : public INetworkServerListener void onTimer() override; void sendMessage(const std::shared_ptr & target, const JsonNode & json); + + void receiveSendChatMessage(const std::shared_ptr & connection, const JsonNode & json); + void receiveAuthentication(const std::shared_ptr & connection, const JsonNode & json); public: LobbyServer(); From 20a38d851480bd91e8615feacdcb825965ce8706 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 27 Dec 2023 17:07:44 +0200 Subject: [PATCH 21/98] Renamed new LobbyXXX classes to GlobaLobbyXXX --- client/mainmenu/CMainMenu.cpp | 2 +- client/serverLobby/LobbyWindow.cpp | 40 +++++++++++++++--------------- client/serverLobby/LobbyWindow.h | 22 ++++++++-------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 77668304c..255b6444b 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -467,7 +467,7 @@ CMultiMode::CMultiMode(ESelectionScreen ScreenType) void CMultiMode::openLobby() { close(); - GH.windows().createAndPushWindow(); + GH.windows().createAndPushWindow(); } void CMultiMode::hostTCP() diff --git a/client/serverLobby/LobbyWindow.cpp b/client/serverLobby/LobbyWindow.cpp index 20eadd17a..b67152942 100644 --- a/client/serverLobby/LobbyWindow.cpp +++ b/client/serverLobby/LobbyWindow.cpp @@ -20,7 +20,7 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/network/NetworkClient.h" -LobbyClient::LobbyClient(LobbyWindow * window) +GlobalLobbyClient::GlobalLobbyClient(GlobalLobbyWindow * window) : networkClient(std::make_unique(*this)) , window(window) {} @@ -42,7 +42,7 @@ static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0) return timeFormatted.toString(); } -void LobbyClient::onPacketReceived(const std::shared_ptr &, const std::vector & message) +void GlobalLobbyClient::onPacketReceived(const std::shared_ptr &, const std::vector & message) { // FIXME: find better approach const char * payloadBegin = reinterpret_cast(message.data()); @@ -69,7 +69,7 @@ void LobbyClient::onPacketReceived(const std::shared_ptr &, c } } -void LobbyClient::onConnectionEstablished(const std::shared_ptr &) +void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr &) { JsonNode toSend; toSend["type"].String() = "authentication"; @@ -78,24 +78,24 @@ void LobbyClient::onConnectionEstablished(const std::shared_ptr &) +void GlobalLobbyClient::onDisconnected(const std::shared_ptr &) { GH.windows().popWindows(1); CInfoWindow::showInfoDialog("Connection to game lobby was lost!", {}); } -void LobbyClient::onTimer() +void GlobalLobbyClient::onTimer() { // no-op } -void LobbyClient::sendMessage(const JsonNode & data) +void GlobalLobbyClient::sendMessage(const JsonNode & data) { std::string payloadString = data.toJson(true); @@ -108,22 +108,22 @@ void LobbyClient::sendMessage(const JsonNode & data) networkClient->sendPacket(payloadBuffer); } -void LobbyClient::start(const std::string & host, uint16_t port) +void GlobalLobbyClient::start(const std::string & host, uint16_t port) { networkClient->start(host, port); } -void LobbyClient::run() +void GlobalLobbyClient::run() { networkClient->run(); } -void LobbyClient::poll() +void GlobalLobbyClient::poll() { networkClient->poll(); } -LobbyWidget::LobbyWidget(LobbyWindow * window) +GlobalLobbyWidget::GlobalLobbyWidget(GlobalLobbyWindow * window) : window(window) { addCallback("closeWindow", [](int) { GH.windows().popWindows(1); }); @@ -133,29 +133,29 @@ LobbyWidget::LobbyWidget(LobbyWindow * window) build(config); } -std::shared_ptr LobbyWidget::getAccountNameLabel() +std::shared_ptr GlobalLobbyWidget::getAccountNameLabel() { return widget("accountNameLabel"); } -std::shared_ptr LobbyWidget::getMessageInput() +std::shared_ptr GlobalLobbyWidget::getMessageInput() { return widget("messageInput"); } -std::shared_ptr LobbyWidget::getGameChat() +std::shared_ptr GlobalLobbyWidget::getGameChat() { return widget("gameChat"); } -LobbyWindow::LobbyWindow(): +GlobalLobbyWindow::GlobalLobbyWindow(): CWindowObject(BORDERED) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - widget = std::make_shared(this); + widget = std::make_shared(this); pos = widget->pos; center(); - connection = std::make_shared(this); + connection = std::make_shared(this); connection->start("127.0.0.1", 30303); widget->getAccountNameLabel()->setText(settings["general"]["playerName"].String()); @@ -163,12 +163,12 @@ LobbyWindow::LobbyWindow(): addUsedEvents(TIME); } -void LobbyWindow::tick(uint32_t msPassed) +void GlobalLobbyWindow::tick(uint32_t msPassed) { connection->poll(); } -void LobbyWindow::doSendChatMessage() +void GlobalLobbyWindow::doSendChatMessage() { std::string messageText = widget->getMessageInput()->getText(); @@ -181,7 +181,7 @@ void LobbyWindow::doSendChatMessage() widget->getMessageInput()->setText(""); } -void LobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when) +void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when) { MetaString chatMessageFormatted; chatMessageFormatted.appendRawString("[%s] {%s}: %s\n"); diff --git a/client/serverLobby/LobbyWindow.h b/client/serverLobby/LobbyWindow.h index e53c45358..eaa0f0396 100644 --- a/client/serverLobby/LobbyWindow.h +++ b/client/serverLobby/LobbyWindow.h @@ -14,23 +14,23 @@ #include "../../lib/network/NetworkListener.h" -class LobbyWindow; +class GlobalLobbyWindow; -class LobbyWidget : public InterfaceObjectConfigurable +class GlobalLobbyWidget : public InterfaceObjectConfigurable { - LobbyWindow * window; + GlobalLobbyWindow * window; public: - LobbyWidget(LobbyWindow * window); + GlobalLobbyWidget(GlobalLobbyWindow * window); std::shared_ptr getAccountNameLabel(); std::shared_ptr getMessageInput(); std::shared_ptr getGameChat(); }; -class LobbyClient : public INetworkClientListener +class GlobalLobbyClient : public INetworkClientListener { std::unique_ptr networkClient; - LobbyWindow * window; + GlobalLobbyWindow * window; void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; @@ -39,7 +39,7 @@ class LobbyClient : public INetworkClientListener void onTimer() override; public: - explicit LobbyClient(LobbyWindow * window); + explicit GlobalLobbyClient(GlobalLobbyWindow * window); void sendMessage(const JsonNode & data); void start(const std::string & host, uint16_t port); @@ -48,17 +48,17 @@ public: }; -class LobbyWindow : public CWindowObject +class GlobalLobbyWindow : public CWindowObject { std::string chatHistory; - std::shared_ptr widget; - std::shared_ptr connection; + std::shared_ptr widget; + std::shared_ptr connection; void tick(uint32_t msPassed); public: - LobbyWindow(); + GlobalLobbyWindow(); void doSendChatMessage(); From 78833a10155df699f647a2e8f3c044eae13af97a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 27 Dec 2023 19:07:49 +0200 Subject: [PATCH 22/98] Split LobbyWindow file into one file per class --- client/CMakeLists.txt | 8 +- .../GlobalLobbyClient.cpp} | 80 ++----------------- .../GlobalLobbyClient.h} | 38 ++------- client/globalLobby/GlobalLobbyWidget.cpp | 42 ++++++++++ client/globalLobby/GlobalLobbyWidget.h | 25 ++++++ client/globalLobby/GlobalLobbyWindow.cpp | 67 ++++++++++++++++ client/globalLobby/GlobalLobbyWindow.h | 32 ++++++++ client/mainmenu/CMainMenu.cpp | 2 +- lobby/LobbyServer.cpp | 29 +++++++ lobby/LobbyServer.h | 2 + 10 files changed, 216 insertions(+), 109 deletions(-) rename client/{serverLobby/LobbyWindow.cpp => globalLobby/GlobalLobbyClient.cpp} (63%) rename client/{serverLobby/LobbyWindow.h => globalLobby/GlobalLobbyClient.h} (55%) create mode 100644 client/globalLobby/GlobalLobbyWidget.cpp create mode 100644 client/globalLobby/GlobalLobbyWidget.h create mode 100644 client/globalLobby/GlobalLobbyWindow.cpp create mode 100644 client/globalLobby/GlobalLobbyWindow.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 4f8c6aaa1..5d39afeb7 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -95,7 +95,9 @@ set(client_SRCS renderSDL/ScreenHandler.cpp renderSDL/SDL_Extensions.cpp - serverLobby/LobbyWindow.cpp + globalLobby/GlobalLobbyClient.cpp + globalLobby/GlobalLobbyWidget.cpp + globalLobby/GlobalLobbyWindow.cpp widgets/Buttons.cpp widgets/CArtifactHolder.cpp @@ -272,7 +274,9 @@ set(client_HEADERS renderSDL/SDL_Extensions.h renderSDL/SDL_PixelAccess.h - serverLobby/LobbyWindow.h + globalLobby/GlobalLobbyClient.h + globalLobby/GlobalLobbyWidget.h + globalLobby/GlobalLobbyWindow.h widgets/Buttons.h widgets/CArtifactHolder.h diff --git a/client/serverLobby/LobbyWindow.cpp b/client/globalLobby/GlobalLobbyClient.cpp similarity index 63% rename from client/serverLobby/LobbyWindow.cpp rename to client/globalLobby/GlobalLobbyClient.cpp index b67152942..eb2d558fa 100644 --- a/client/serverLobby/LobbyWindow.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -1,5 +1,5 @@ /* - * LobbyWindow.cpp, part of VCMI engine + * GlobalLobbyClient.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -9,17 +9,20 @@ */ #include "StdInc.h" -#include "LobbyWindow.h" +#include "GlobalLobbyClient.h" + +#include "GlobalLobbyWindow.h" #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" -#include "../widgets/TextControls.h" #include "../windows/InfoWindows.h" #include "../../lib/MetaString.h" #include "../../lib/CConfigHandler.h" #include "../../lib/network/NetworkClient.h" +GlobalLobbyClient::~GlobalLobbyClient() = default; + GlobalLobbyClient::GlobalLobbyClient(GlobalLobbyWindow * window) : networkClient(std::make_unique(*this)) , window(window) @@ -122,74 +125,3 @@ void GlobalLobbyClient::poll() { networkClient->poll(); } - -GlobalLobbyWidget::GlobalLobbyWidget(GlobalLobbyWindow * window) - : window(window) -{ - addCallback("closeWindow", [](int) { GH.windows().popWindows(1); }); - addCallback("sendMessage", [this](int) { this->window->doSendChatMessage(); }); - - const JsonNode config(JsonPath::builtin("config/widgets/lobbyWindow.json")); - build(config); -} - -std::shared_ptr GlobalLobbyWidget::getAccountNameLabel() -{ - return widget("accountNameLabel"); -} - -std::shared_ptr GlobalLobbyWidget::getMessageInput() -{ - return widget("messageInput"); -} - -std::shared_ptr GlobalLobbyWidget::getGameChat() -{ - return widget("gameChat"); -} - -GlobalLobbyWindow::GlobalLobbyWindow(): - CWindowObject(BORDERED) -{ - OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - widget = std::make_shared(this); - pos = widget->pos; - center(); - connection = std::make_shared(this); - - connection->start("127.0.0.1", 30303); - widget->getAccountNameLabel()->setText(settings["general"]["playerName"].String()); - - addUsedEvents(TIME); -} - -void GlobalLobbyWindow::tick(uint32_t msPassed) -{ - connection->poll(); -} - -void GlobalLobbyWindow::doSendChatMessage() -{ - std::string messageText = widget->getMessageInput()->getText(); - - JsonNode toSend; - toSend["type"].String() = "sendChatMessage"; - toSend["messageText"].String() = messageText; - - connection->sendMessage(toSend); - - widget->getMessageInput()->setText(""); -} - -void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when) -{ - MetaString chatMessageFormatted; - chatMessageFormatted.appendRawString("[%s] {%s}: %s\n"); - chatMessageFormatted.replaceRawString(when); - chatMessageFormatted.replaceRawString(sender); - chatMessageFormatted.replaceRawString(message); - - chatHistory += chatMessageFormatted.toString(); - - widget->getGameChat()->setText(chatHistory); -} diff --git a/client/serverLobby/LobbyWindow.h b/client/globalLobby/GlobalLobbyClient.h similarity index 55% rename from client/serverLobby/LobbyWindow.h rename to client/globalLobby/GlobalLobbyClient.h index eaa0f0396..c018792ff 100644 --- a/client/serverLobby/LobbyWindow.h +++ b/client/globalLobby/GlobalLobbyClient.h @@ -1,5 +1,5 @@ /* - * LobbyWindow.h, part of VCMI engine + * GlobalLobbyClient.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -9,24 +9,14 @@ */ #pragma once -#include "../gui/InterfaceObjectConfigurable.h" -#include "../windows/CWindowObject.h" - #include "../../lib/network/NetworkListener.h" +VCMI_LIB_NAMESPACE_BEGIN +class JsonNode; +VCMI_LIB_NAMESPACE_END + class GlobalLobbyWindow; -class GlobalLobbyWidget : public InterfaceObjectConfigurable -{ - GlobalLobbyWindow * window; -public: - GlobalLobbyWidget(GlobalLobbyWindow * window); - - std::shared_ptr getAccountNameLabel(); - std::shared_ptr getMessageInput(); - std::shared_ptr getGameChat(); -}; - class GlobalLobbyClient : public INetworkClientListener { std::unique_ptr networkClient; @@ -40,6 +30,7 @@ class GlobalLobbyClient : public INetworkClientListener public: explicit GlobalLobbyClient(GlobalLobbyWindow * window); + ~GlobalLobbyClient(); void sendMessage(const JsonNode & data); void start(const std::string & host, uint16_t port); @@ -47,20 +38,3 @@ public: void poll(); }; - -class GlobalLobbyWindow : public CWindowObject -{ - std::string chatHistory; - - std::shared_ptr widget; - std::shared_ptr connection; - - void tick(uint32_t msPassed); - -public: - GlobalLobbyWindow(); - - void doSendChatMessage(); - - void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when); -}; diff --git a/client/globalLobby/GlobalLobbyWidget.cpp b/client/globalLobby/GlobalLobbyWidget.cpp new file mode 100644 index 000000000..6491d1a24 --- /dev/null +++ b/client/globalLobby/GlobalLobbyWidget.cpp @@ -0,0 +1,42 @@ +/* + * GlobalLobbyWidget.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "GlobalLobbyWidget.h" +#include "GlobalLobbyWindow.h" + +#include "../gui/CGuiHandler.h" +#include "../gui/WindowHandler.h" +#include "../widgets/TextControls.h" + +GlobalLobbyWidget::GlobalLobbyWidget(GlobalLobbyWindow * window) + : window(window) +{ + addCallback("closeWindow", [](int) { GH.windows().popWindows(1); }); + addCallback("sendMessage", [this](int) { this->window->doSendChatMessage(); }); + + const JsonNode config(JsonPath::builtin("config/widgets/lobbyWindow.json")); + build(config); +} + +std::shared_ptr GlobalLobbyWidget::getAccountNameLabel() +{ + return widget("accountNameLabel"); +} + +std::shared_ptr GlobalLobbyWidget::getMessageInput() +{ + return widget("messageInput"); +} + +std::shared_ptr GlobalLobbyWidget::getGameChat() +{ + return widget("gameChat"); +} diff --git a/client/globalLobby/GlobalLobbyWidget.h b/client/globalLobby/GlobalLobbyWidget.h new file mode 100644 index 000000000..c22c0deae --- /dev/null +++ b/client/globalLobby/GlobalLobbyWidget.h @@ -0,0 +1,25 @@ +/* + * GlobalLobbyWidget.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../gui/InterfaceObjectConfigurable.h" + +class GlobalLobbyWindow; + +class GlobalLobbyWidget : public InterfaceObjectConfigurable +{ + GlobalLobbyWindow * window; +public: + GlobalLobbyWidget(GlobalLobbyWindow * window); + + std::shared_ptr getAccountNameLabel(); + std::shared_ptr getMessageInput(); + std::shared_ptr getGameChat(); +}; diff --git a/client/globalLobby/GlobalLobbyWindow.cpp b/client/globalLobby/GlobalLobbyWindow.cpp new file mode 100644 index 000000000..8ea9dad64 --- /dev/null +++ b/client/globalLobby/GlobalLobbyWindow.cpp @@ -0,0 +1,67 @@ +/* + * GlobalLobbyWindow.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "GlobalLobbyWindow.h" + +#include "GlobalLobbyWidget.h" +#include "GlobalLobbyClient.h" + +#include "../gui/CGuiHandler.h" +#include "../widgets/TextControls.h" + +#include "../../lib/MetaString.h" +#include "../../lib/CConfigHandler.h" + +GlobalLobbyWindow::GlobalLobbyWindow(): + CWindowObject(BORDERED) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + widget = std::make_shared(this); + pos = widget->pos; + center(); + connection = std::make_shared(this); + + connection->start("127.0.0.1", 30303); + widget->getAccountNameLabel()->setText(settings["general"]["playerName"].String()); + + addUsedEvents(TIME); +} + +void GlobalLobbyWindow::tick(uint32_t msPassed) +{ + connection->poll(); +} + +void GlobalLobbyWindow::doSendChatMessage() +{ + std::string messageText = widget->getMessageInput()->getText(); + + JsonNode toSend; + toSend["type"].String() = "sendChatMessage"; + toSend["messageText"].String() = messageText; + + connection->sendMessage(toSend); + + widget->getMessageInput()->setText(""); +} + +void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when) +{ + MetaString chatMessageFormatted; + chatMessageFormatted.appendRawString("[%s] {%s}: %s\n"); + chatMessageFormatted.replaceRawString(when); + chatMessageFormatted.replaceRawString(sender); + chatMessageFormatted.replaceRawString(message); + + chatHistory += chatMessageFormatted.toString(); + + widget->getGameChat()->setText(chatHistory); +} diff --git a/client/globalLobby/GlobalLobbyWindow.h b/client/globalLobby/GlobalLobbyWindow.h new file mode 100644 index 000000000..9856d96cb --- /dev/null +++ b/client/globalLobby/GlobalLobbyWindow.h @@ -0,0 +1,32 @@ +/* + * GlobalLobbyWindow.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../windows/CWindowObject.h" + +class GlobalLobbyWidget; +class GlobalLobbyClient; + +class GlobalLobbyWindow : public CWindowObject +{ + std::string chatHistory; + + std::shared_ptr widget; + std::shared_ptr connection; + + void tick(uint32_t msPassed); + +public: + GlobalLobbyWindow(); + + void doSendChatMessage(); + + void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when); +}; diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 255b6444b..6938666cf 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -24,7 +24,7 @@ #include "../gui/Shortcut.h" #include "../gui/WindowHandler.h" #include "../render/Canvas.h" -#include "../serverLobby/LobbyWindow.h" +#include "../globalLobby/GlobalLobbyWindow.h" #include "../widgets/CComponent.h" #include "../widgets/Buttons.h" #include "../widgets/MiscWidgets.h" diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 29cdce371..3ad7393f5 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -78,6 +78,11 @@ void LobbyDatabase::insertChatMessage(const std::string & sender, const std::str insertChatMessageStatement->reset(); } +bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountName) +{ + return false; //TODO +} + std::vector LobbyDatabase::getRecentMessageHistory() { std::vector result; @@ -132,6 +137,9 @@ void LobbyServer::onPacketReceived(const std::shared_ptr & co if (json["type"].String() == "authentication") return receiveAuthentication(connection, json); + + if (json["type"].String() == "joinGameRoom") + return receiveJoinGameRoom(connection, json); } void LobbyServer::receiveSendChatMessage(const std::shared_ptr & connection, const JsonNode & json) @@ -157,6 +165,12 @@ void LobbyServer::receiveAuthentication(const std::shared_ptr { std::string accountName = json["accountName"].String(); + // TODO: account cookie check + // TODO: account password check + // TODO: protocol version number + // TODO: client/server mode flag + // TODO: client language + activeAccounts[connection].accountName = accountName; auto history = database->getRecentMessageHistory(); @@ -178,6 +192,21 @@ void LobbyServer::receiveAuthentication(const std::shared_ptr sendMessage(connection, reply); } +void LobbyServer::receiveJoinGameRoom(const std::shared_ptr & connection, const JsonNode & json) +{ + if (activeAccounts.count(connection) == 0) + return; // unauthenticated + + std::string senderName = activeAccounts[connection].accountName; + + if (database->isPlayerInGameRoom(senderName)) + return; // only 1 room per player allowed + + // TODO: roomType: private, public + // TODO: additional flags, e.g. allowCheats + // TODO: connection mode: direct or proxy +} + LobbyServer::LobbyServer() : database(new LobbyDatabase()) , networkServer(new NetworkServer(*this)) diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index da334ee7f..3c79a6b78 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -39,6 +39,7 @@ public: void insertChatMessage(const std::string & sender, const std::string & messageText); std::vector getRecentMessageHistory(); + bool isPlayerInGameRoom(const std::string & accountName); }; class LobbyServer : public INetworkServerListener @@ -62,6 +63,7 @@ class LobbyServer : public INetworkServerListener void receiveSendChatMessage(const std::shared_ptr & connection, const JsonNode & json); void receiveAuthentication(const std::shared_ptr & connection, const JsonNode & json); + void receiveJoinGameRoom(const std::shared_ptr & connection, const JsonNode & json); public: LobbyServer(); From 55b504792e64978220b7e5f0bf069522ab4afb3b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 28 Dec 2023 21:27:21 +0200 Subject: [PATCH 23/98] Implemented basic version of login window and persistent connection on client --- Mods/vcmi/config/vcmi/english.json | 5 ++ client/CMakeLists.txt | 2 + client/CServerHandler.cpp | 7 ++ client/CServerHandler.h | 4 + client/globalLobby/GlobalLobbyClient.cpp | 78 +++++++++++++++---- client/globalLobby/GlobalLobbyClient.h | 19 +++-- client/globalLobby/GlobalLobbyLoginWindow.cpp | 76 ++++++++++++++++++ client/globalLobby/GlobalLobbyLoginWindow.h | 41 ++++++++++ client/globalLobby/GlobalLobbyWidget.cpp | 1 + client/globalLobby/GlobalLobbyWindow.cpp | 23 +++--- client/globalLobby/GlobalLobbyWindow.h | 6 +- client/lobby/CSelectionBase.cpp | 2 +- client/lobby/OptionsTab.cpp | 2 +- client/mainmenu/CHighScoreScreen.cpp | 4 +- client/mainmenu/CMainMenu.cpp | 7 +- client/widgets/TextControls.cpp | 4 +- client/widgets/TextControls.h | 2 +- client/windows/CSpellWindow.cpp | 2 +- client/windows/GUIClasses.cpp | 4 +- config/widgets/lobbyWindow.json | 2 + lib/network/NetworkClient.cpp | 9 +++ lib/network/NetworkClient.h | 2 + lobby/LobbyServer.cpp | 35 +++++---- 23 files changed, 278 insertions(+), 59 deletions(-) create mode 100644 client/globalLobby/GlobalLobbyLoginWindow.cpp create mode 100644 client/globalLobby/GlobalLobbyLoginWindow.h diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 81215b65c..0cc354008 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -71,6 +71,11 @@ "vcmi.lobby.noPreview" : "no preview", "vcmi.lobby.noUnderground" : "no underground", "vcmi.lobby.sortDate" : "Sorts maps by change date", + + "vcmi.lobby.login.title" : "VCMI Lobby", + "vcmi.lobby.login.username" : "Username:", + "vcmi.lobby.login.connecting" : "Connecting...", +// "vcmi.lobby.login.connectionFailed" : "Connection failed: %s", "vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.", "vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.", diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 5d39afeb7..7d00fee8c 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -96,6 +96,7 @@ set(client_SRCS renderSDL/SDL_Extensions.cpp globalLobby/GlobalLobbyClient.cpp + globalLobby/GlobalLobbyLoginWindow.cpp globalLobby/GlobalLobbyWidget.cpp globalLobby/GlobalLobbyWindow.cpp @@ -275,6 +276,7 @@ set(client_HEADERS renderSDL/SDL_PixelAccess.h globalLobby/GlobalLobbyClient.h + globalLobby/GlobalLobbyLoginWindow.h globalLobby/GlobalLobbyWidget.h globalLobby/GlobalLobbyWindow.h diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 0719af7fa..165c6f902 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -16,6 +16,7 @@ #include "gui/CGuiHandler.h" #include "gui/WindowHandler.h" +#include "globalLobby/GlobalLobbyClient.h" #include "lobby/CSelectionBase.h" #include "lobby/CLobbyScreen.h" #include "windows/InfoWindows.h" @@ -139,6 +140,7 @@ CServerHandler::CServerHandler() : state(EClientState::NONE) , networkClient(std::make_unique(*this)) , applier(std::make_unique>()) + , lobbyClient(std::make_unique()) , client(nullptr) , loadMode(0) , campaignStateToSend(nullptr) @@ -175,6 +177,11 @@ void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std:: myNames.push_back(settings["general"]["playerName"].String()); } +GlobalLobbyClient & CServerHandler::getGlobalLobby() +{ + return *lobbyClient; +} + void CServerHandler::startLocalServerAndConnect() { if(threadRunLocalServer) diff --git a/client/CServerHandler.h b/client/CServerHandler.h index fd3dd1145..bca2f4608 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -35,6 +35,7 @@ VCMI_LIB_NAMESPACE_END class CClient; class CBaseForLobbyApply; +class GlobalLobbyClient; class HighScoreCalculation; class HighScoreParameter; @@ -86,6 +87,7 @@ class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClien friend class ApplyOnLobbyHandlerNetPackVisitor; std::unique_ptr networkClient; + std::unique_ptr lobbyClient; std::unique_ptr> applier; std::shared_ptr mapToStart; std::vector myNames; @@ -138,6 +140,8 @@ public: void startLocalServerAndConnect(); void connectToServer(const std::string & addr, const ui16 port); + GlobalLobbyClient & getGlobalLobby(); + // Helpers for lobby state access std::set getHumanColors(); PlayerColor myFirstColor() const; diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index eb2d558fa..fd3ba1ac8 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -12,6 +12,7 @@ #include "GlobalLobbyClient.h" #include "GlobalLobbyWindow.h" +#include "GlobalLobbyLoginWindow.h" #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" @@ -21,12 +22,19 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/network/NetworkClient.h" -GlobalLobbyClient::~GlobalLobbyClient() = default; +GlobalLobbyClient::~GlobalLobbyClient() +{ + networkClient->stop(); + networkThread->join(); +} -GlobalLobbyClient::GlobalLobbyClient(GlobalLobbyWindow * window) +GlobalLobbyClient::GlobalLobbyClient() : networkClient(std::make_unique(*this)) - , window(window) -{} +{ + networkThread = std::make_unique([this](){ + networkClient->run(); + }); +} static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0) { @@ -47,10 +55,22 @@ static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0) void GlobalLobbyClient::onPacketReceived(const std::shared_ptr &, const std::vector & message) { + boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); + // FIXME: find better approach const char * payloadBegin = reinterpret_cast(message.data()); JsonNode json(payloadBegin, message.size()); + if (json["type"].String() == "authentication") + { + auto loginWindowPtr = loginWindow.lock(); + + if (!loginWindowPtr || !GH.windows().topWindow()) + throw std::runtime_error("lobby connection finished without active login window!"); + + loginWindowPtr->onConnectionSuccess(); + } + if (json["type"].String() == "chatHistory") { for (auto const & entry : json["messages"].Vector()) @@ -59,7 +79,10 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptronGameChatMessage(senderName, messageText, timeFormatted); + + auto lobbyWindowPtr = lobbyWindow.lock(); + if(lobbyWindowPtr) + lobbyWindowPtr->onGameChatMessage(senderName, messageText, timeFormatted); } } @@ -68,7 +91,9 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptronGameChatMessage(senderName, messageText, timeFormatted); + auto lobbyWindowPtr = lobbyWindow.lock(); + if(lobbyWindowPtr) + lobbyWindowPtr->onGameChatMessage(senderName, messageText, timeFormatted); } } @@ -83,8 +108,13 @@ void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr()) + throw std::runtime_error("lobby connection failed without active login window!"); + + logGlobal->warn("Connection to game lobby failed! Reason: %s", errorMessage); + loginWindowPtr->onConnectionFailed(errorMessage); } void GlobalLobbyClient::onDisconnected(const std::shared_ptr &) @@ -111,17 +141,37 @@ void GlobalLobbyClient::sendMessage(const JsonNode & data) networkClient->sendPacket(payloadBuffer); } -void GlobalLobbyClient::start(const std::string & host, uint16_t port) +void GlobalLobbyClient::connect() { - networkClient->start(host, port); + networkClient->start("127.0.0.1", 30303); } -void GlobalLobbyClient::run() +bool GlobalLobbyClient::isConnected() { - networkClient->run(); + return networkClient->isConnected(); } -void GlobalLobbyClient::poll() +std::shared_ptr GlobalLobbyClient::createLoginWindow() { - networkClient->poll(); + auto loginWindowPtr = loginWindow.lock(); + if (loginWindowPtr) + return loginWindowPtr; + + auto loginWindowNew = std::make_shared(); + loginWindow = loginWindowNew; + + return loginWindowNew; } + +std::shared_ptr GlobalLobbyClient::createLobbyWindow() +{ + auto lobbyWindowPtr = lobbyWindow.lock(); + if (lobbyWindowPtr) + return lobbyWindowPtr; + + lobbyWindowPtr = std::make_shared(); + lobbyWindow = lobbyWindowPtr; + lobbyWindowLock = lobbyWindowPtr; + return lobbyWindowPtr; +} + diff --git a/client/globalLobby/GlobalLobbyClient.h b/client/globalLobby/GlobalLobbyClient.h index c018792ff..50e1020d8 100644 --- a/client/globalLobby/GlobalLobbyClient.h +++ b/client/globalLobby/GlobalLobbyClient.h @@ -15,12 +15,17 @@ VCMI_LIB_NAMESPACE_BEGIN class JsonNode; VCMI_LIB_NAMESPACE_END +class GlobalLobbyLoginWindow; class GlobalLobbyWindow; -class GlobalLobbyClient : public INetworkClientListener +class GlobalLobbyClient : public INetworkClientListener, boost::noncopyable { + std::unique_ptr networkThread; std::unique_ptr networkClient; - GlobalLobbyWindow * window; + + std::weak_ptr loginWindow; + std::weak_ptr lobbyWindow; + std::shared_ptr lobbyWindowLock; // helper strong reference to prevent window destruction on closing void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; @@ -29,12 +34,12 @@ class GlobalLobbyClient : public INetworkClientListener void onTimer() override; public: - explicit GlobalLobbyClient(GlobalLobbyWindow * window); + explicit GlobalLobbyClient(); ~GlobalLobbyClient(); void sendMessage(const JsonNode & data); - void start(const std::string & host, uint16_t port); - void run(); - void poll(); - + void connect(); + bool isConnected(); + std::shared_ptr createLoginWindow(); + std::shared_ptr createLobbyWindow(); }; diff --git a/client/globalLobby/GlobalLobbyLoginWindow.cpp b/client/globalLobby/GlobalLobbyLoginWindow.cpp new file mode 100644 index 000000000..6e4056d68 --- /dev/null +++ b/client/globalLobby/GlobalLobbyLoginWindow.cpp @@ -0,0 +1,76 @@ +/* + * GlobalLobbyLoginWindow.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "GlobalLobbyLoginWindow.h" + +#include "GlobalLobbyClient.h" +#include "GlobalLobbyWindow.h" + +#include "../gui/CGuiHandler.h" +#include "../gui/WindowHandler.h" +#include "../widgets/TextControls.h" +#include "../widgets/Images.h" +#include "../widgets/Buttons.h" +#include "../widgets/MiscWidgets.h" +#include "../CGameInfo.h" +#include "../CServerHandler.h" + +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/MetaString.h" + +GlobalLobbyLoginWindow::GlobalLobbyLoginWindow() + : CWindowObject(BORDERED) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + + pos.w = 200; + pos.h = 200; + + background = std::make_shared(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h)); + labelTitle = std::make_shared( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.login.title")); + labelUsername = std::make_shared( 10, 45, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.lobby.login.username")); + backgroundUsername = std::make_shared(Rect(10, 70, 180, 20), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64)); + inputUsername = std::make_shared(Rect(15, 73, 176, 16), FONT_SMALL, nullptr, ETextAlignment::TOPLEFT, true); + buttonLogin = std::make_shared(Point(10, 160), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onLogin(); }); + buttonClose = std::make_shared(Point(126, 160), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }); + labelStatus = std::make_shared( "", Rect(15, 95, 175, 60), 1, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE); + + background->playerColored(PlayerColor(1)); + + center(); +} + +void GlobalLobbyLoginWindow::onClose() +{ + close(); + // TODO: abort ongoing connection attempt, if any +} + +void GlobalLobbyLoginWindow::onLogin() +{ + labelStatus->setText(CGI->generaltexth->translate("vcmi.lobby.login.connecting")); + CSH->getGlobalLobby().connect(); +} + +void GlobalLobbyLoginWindow::onConnectionSuccess() +{ + close(); + GH.windows().pushWindow(CSH->getGlobalLobby().createLobbyWindow()); +} + +void GlobalLobbyLoginWindow::onConnectionFailed(const std::string & reason) +{ + MetaString formatter; + formatter.appendTextID("vcmi.lobby.login.error"); + formatter.replaceRawString(reason); + + labelStatus->setText(formatter.toString()); +} diff --git a/client/globalLobby/GlobalLobbyLoginWindow.h b/client/globalLobby/GlobalLobbyLoginWindow.h new file mode 100644 index 000000000..31bcd5015 --- /dev/null +++ b/client/globalLobby/GlobalLobbyLoginWindow.h @@ -0,0 +1,41 @@ +/* + * GlobalLobbyLoginWindow.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../windows/CWindowObject.h" + +class CLabel; +class CTextBox; +class CTextInput; +class FilledTexturePlayerColored; +class TransparentFilledRectangle; +class CButton; + +class GlobalLobbyLoginWindow : public CWindowObject +{ + std::shared_ptr background; + std::shared_ptr labelTitle; + std::shared_ptr labelUsername; + std::shared_ptr labelStatus; + std::shared_ptr backgroundUsername; + std::shared_ptr inputUsername; + + std::shared_ptr buttonLogin; + std::shared_ptr buttonClose; + + void onClose(); + void onLogin(); + +public: + GlobalLobbyLoginWindow(); + + void onConnectionSuccess(); + void onConnectionFailed(const std::string & reason); +}; diff --git a/client/globalLobby/GlobalLobbyWidget.cpp b/client/globalLobby/GlobalLobbyWidget.cpp index 6491d1a24..e5b462721 100644 --- a/client/globalLobby/GlobalLobbyWidget.cpp +++ b/client/globalLobby/GlobalLobbyWidget.cpp @@ -21,6 +21,7 @@ GlobalLobbyWidget::GlobalLobbyWidget(GlobalLobbyWindow * window) { addCallback("closeWindow", [](int) { GH.windows().popWindows(1); }); addCallback("sendMessage", [this](int) { this->window->doSendChatMessage(); }); + addCallback("createGameRoom", [this](int) { this->window->doCreateGameRoom(); }); const JsonNode config(JsonPath::builtin("config/widgets/lobbyWindow.json")); build(config); diff --git a/client/globalLobby/GlobalLobbyWindow.cpp b/client/globalLobby/GlobalLobbyWindow.cpp index 8ea9dad64..fa8b7bd1d 100644 --- a/client/globalLobby/GlobalLobbyWindow.cpp +++ b/client/globalLobby/GlobalLobbyWindow.cpp @@ -16,6 +16,7 @@ #include "../gui/CGuiHandler.h" #include "../widgets/TextControls.h" +#include "../CServerHandler.h" #include "../../lib/MetaString.h" #include "../../lib/CConfigHandler.h" @@ -27,17 +28,8 @@ GlobalLobbyWindow::GlobalLobbyWindow(): widget = std::make_shared(this); pos = widget->pos; center(); - connection = std::make_shared(this); - connection->start("127.0.0.1", 30303); widget->getAccountNameLabel()->setText(settings["general"]["playerName"].String()); - - addUsedEvents(TIME); -} - -void GlobalLobbyWindow::tick(uint32_t msPassed) -{ - connection->poll(); } void GlobalLobbyWindow::doSendChatMessage() @@ -48,11 +40,22 @@ void GlobalLobbyWindow::doSendChatMessage() toSend["type"].String() = "sendChatMessage"; toSend["messageText"].String() = messageText; - connection->sendMessage(toSend); + CSH->getGlobalLobby().sendMessage(toSend); widget->getMessageInput()->setText(""); } +void GlobalLobbyWindow::doCreateGameRoom() +{ + // TODO: + // start local server and supply our UUID / client credentials to it + // server logs into lobby ( uuid = client, mode = server ). This creates 'room' in mode 'empty' + // server starts accepting connections from players (including host) + // client connects to local server + // client sends createGameRoom query to lobby with own / server UUID and mode 'direct' (non-proxy) + // client requests to change room status to private or public +} + void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when) { MetaString chatMessageFormatted; diff --git a/client/globalLobby/GlobalLobbyWindow.h b/client/globalLobby/GlobalLobbyWindow.h index 9856d96cb..5958ab495 100644 --- a/client/globalLobby/GlobalLobbyWindow.h +++ b/client/globalLobby/GlobalLobbyWindow.h @@ -12,21 +12,19 @@ #include "../windows/CWindowObject.h" class GlobalLobbyWidget; -class GlobalLobbyClient; class GlobalLobbyWindow : public CWindowObject { std::string chatHistory; std::shared_ptr widget; - std::shared_ptr connection; - - void tick(uint32_t msPassed); public: GlobalLobbyWindow(); void doSendChatMessage(); + void doCreateGameRoom(); void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when); + }; diff --git a/client/lobby/CSelectionBase.cpp b/client/lobby/CSelectionBase.cpp index 6ead7387c..56e814663 100644 --- a/client/lobby/CSelectionBase.cpp +++ b/client/lobby/CSelectionBase.cpp @@ -335,7 +335,7 @@ CChatBox::CChatBox(const Rect & rect) Rect textInputArea(1, rect.h - height, rect.w - 1, height); Rect chatHistoryArea(3, 1, rect.w - 3, rect.h - height - 1); inputBackground = std::make_shared(textInputArea, ColorRGBA(0,0,0,192)); - inputBox = std::make_shared(textInputArea, EFonts::FONT_SMALL, 0); + inputBox = std::make_shared(textInputArea, EFonts::FONT_SMALL, nullptr, ETextAlignment::TOPLEFT, true); inputBox->removeUsedEvents(KEYBOARD); chatHistory = std::make_shared("", chatHistoryArea, 1); diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index f67d3d447..99f6851f7 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -892,7 +892,7 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con labelPlayerName = std::make_shared(55, 10, EFonts::FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, name, 95); else { - labelPlayerNameEdit = std::make_shared(Rect(6, 3, 95, 15), EFonts::FONT_SMALL, nullptr, false); + labelPlayerNameEdit = std::make_shared(Rect(6, 3, 95, 15), EFonts::FONT_SMALL, nullptr, ETextAlignment::CENTER, false); labelPlayerNameEdit->setText(name); } labelWhoCanPlay = std::make_shared(Rect(6, 23, 45, (int)graphics->fonts[EFonts::FONT_TINY]->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]); diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 07b119950..4abcc712b 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -372,7 +372,7 @@ CHighScoreInput::CHighScoreInput(std::string playerName, std::function(Point(26, 142), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CHighScoreInput::okay, this), EShortcut::GLOBAL_ACCEPT); buttonCancel = std::make_shared(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CHighScoreInput::abort, this), EShortcut::GLOBAL_CANCEL); statusBar = CGStatusBar::create(std::make_shared(background->getSurface(), Rect(7, 186, 218, 18), 7, 186)); - textInput = std::make_shared(Rect(18, 104, 200, 25), FONT_SMALL, 0); + textInput = std::make_shared(Rect(18, 104, 200, 25), FONT_SMALL, nullptr, ETextAlignment::CENTER, true); textInput->setText(playerName); } @@ -384,4 +384,4 @@ void CHighScoreInput::okay() void CHighScoreInput::abort() { ready(""); -} \ No newline at end of file +} diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 6938666cf..c7e3fdcb5 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -24,6 +24,8 @@ #include "../gui/Shortcut.h" #include "../gui/WindowHandler.h" #include "../render/Canvas.h" +#include "../globalLobby/GlobalLobbyLoginWindow.h" +#include "../globalLobby/GlobalLobbyClient.h" #include "../globalLobby/GlobalLobbyWindow.h" #include "../widgets/CComponent.h" #include "../widgets/Buttons.h" @@ -467,7 +469,10 @@ CMultiMode::CMultiMode(ESelectionScreen ScreenType) void CMultiMode::openLobby() { close(); - GH.windows().createAndPushWindow(); + if (CSH->getGlobalLobby().isConnected()) + GH.windows().pushWindow(CSH->getGlobalLobby().createLobbyWindow()); + else + GH.windows().pushWindow(CSH->getGlobalLobby().createLoginWindow()); } void CMultiMode::hostTCP() diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index 0e9fad026..d58576eba 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -551,8 +551,8 @@ Point CGStatusBar::getBorderSize() return Point(); } -CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList & CB, bool giveFocusToInput) - : CLabel(Pos.x, Pos.y, font, ETextAlignment::CENTER), +CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList & CB, ETextAlignment alignment, bool giveFocusToInput) + : CLabel(Pos.x, Pos.y, font, alignment), cb(CB), CFocusable(std::make_shared(this)) { diff --git a/client/widgets/TextControls.h b/client/widgets/TextControls.h index 7c6b5266b..264158ced 100644 --- a/client/widgets/TextControls.h +++ b/client/widgets/TextControls.h @@ -228,7 +228,7 @@ public: void setText(const std::string & nText, bool callCb); void setHelpText(const std::string &); - CTextInput(const Rect & Pos, EFonts font, const CFunctionList & CB, bool giveFocusToInput = true); + CTextInput(const Rect & Pos, EFonts font, const CFunctionList & CB, ETextAlignment alignment, bool giveFocusToInput); CTextInput(const Rect & Pos, const Point & bgOffset, const ImagePath & bgName, const CFunctionList & CB); CTextInput(const Rect & Pos, std::shared_ptr srf); diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index fbbc8f5e3..1fd66293a 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -136,7 +136,7 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m searchBoxRectangle = std::make_shared(r.resize(1), rectangleColor, borderColor); searchBoxDescription = std::make_shared(r.center().x, r.center().y, FONT_SMALL, ETextAlignment::CENTER, grayedColor, CGI->generaltexth->translate("vcmi.spellBook.search")); - searchBox = std::make_shared(r, FONT_SMALL, std::bind(&CSpellWindow::searchInput, this)); + searchBox = std::make_shared(r, FONT_SMALL, std::bind(&CSpellWindow::searchInput, this), ETextAlignment::CENTER, true); } processSpells(); diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 94c11a178..509f70413 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -324,8 +324,8 @@ CSplitWindow::CSplitWindow(const CCreature * creature, std::function(Rect(20, 218, 100, 36), FONT_BIG, std::bind(&CSplitWindow::setAmountText, this, _1, true)); - rightInput = std::make_shared(Rect(176, 218, 100, 36), FONT_BIG, std::bind(&CSplitWindow::setAmountText, this, _1, false)); + leftInput = std::make_shared(Rect(20, 218, 100, 36), FONT_BIG, std::bind(&CSplitWindow::setAmountText, this, _1, true), ETextAlignment::CENTER, true); + rightInput = std::make_shared(Rect(176, 218, 100, 36), FONT_BIG, std::bind(&CSplitWindow::setAmountText, this, _1, false), ETextAlignment::CENTER, true); //add filters to allow only number input leftInput->filters += std::bind(&CTextInput::numberFilter, _1, _2, leftMin, leftMax); diff --git a/config/widgets/lobbyWindow.json b/config/widgets/lobbyWindow.json index 9c98d5e0e..2b81f3dbe 100644 --- a/config/widgets/lobbyWindow.json +++ b/config/widgets/lobbyWindow.json @@ -158,6 +158,7 @@ "position": {"x": 10, "y": 520}, "image": "settingsWindow/button190", "help": "core.help.288", + "callback": "createGameRoom", "items": [ { @@ -175,6 +176,7 @@ "position": {"x": 10, "y": 555}, "image": "settingsWindow/button190", "help": "core.help.288", + "callback": "createGameRoom", "items": [ { diff --git a/lib/network/NetworkClient.cpp b/lib/network/NetworkClient.cpp index 44624e0d9..25462cf01 100644 --- a/lib/network/NetworkClient.cpp +++ b/lib/network/NetworkClient.cpp @@ -22,6 +22,9 @@ NetworkClient::NetworkClient(INetworkClientListener & listener) void NetworkClient::start(const std::string & host, uint16_t port) { + if (isConnected()) + throw std::runtime_error("Attempting to connect while already connected!"); + boost::asio::ip::tcp::resolver resolver(*io); auto endpoints = resolver.resolve(host, std::to_string(port)); @@ -58,6 +61,11 @@ void NetworkClient::stop() io->stop(); } +bool NetworkClient::isConnected() const +{ + return connection != nullptr; +} + void NetworkClient::setTimer(std::chrono::milliseconds duration) { auto timer = std::make_shared(*io, duration); @@ -74,6 +82,7 @@ void NetworkClient::sendPacket(const std::vector & message) void NetworkClient::onDisconnected(const std::shared_ptr & connection) { + this->connection.reset(); listener.onDisconnected(connection); } diff --git a/lib/network/NetworkClient.h b/lib/network/NetworkClient.h index ef45af7c6..cc244aa50 100644 --- a/lib/network/NetworkClient.h +++ b/lib/network/NetworkClient.h @@ -33,6 +33,8 @@ public: NetworkClient(INetworkClientListener & listener); virtual ~NetworkClient() = default; + bool isConnected() const; + void setTimer(std::chrono::milliseconds duration); void sendPacket(const std::vector & message); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 3ad7393f5..67627b447 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -173,23 +173,32 @@ void LobbyServer::receiveAuthentication(const std::shared_ptr activeAccounts[connection].accountName = accountName; - auto history = database->getRecentMessageHistory(); - - JsonNode reply; - reply["type"].String() = "chatHistory"; - - for (auto const & message : boost::adaptors::reverse(history)) { - JsonNode jsonEntry; + JsonNode reply; + reply["type"].String() = "authentication"; - jsonEntry["messageText"].String() = message.messageText; - jsonEntry["senderName"].String() = message.sender; - jsonEntry["ageSeconds"].Integer() = message.messageAgeSeconds; - - reply["messages"].Vector().push_back(jsonEntry); + sendMessage(connection, reply); } - sendMessage(connection, reply); + auto history = database->getRecentMessageHistory(); + + { + JsonNode reply; + reply["type"].String() = "chatHistory"; + + for (auto const & message : boost::adaptors::reverse(history)) + { + JsonNode jsonEntry; + + jsonEntry["messageText"].String() = message.messageText; + jsonEntry["senderName"].String() = message.sender; + jsonEntry["ageSeconds"].Integer() = message.messageAgeSeconds; + + reply["messages"].Vector().push_back(jsonEntry); + } + + sendMessage(connection, reply); + } } void LobbyServer::receiveJoinGameRoom(const std::shared_ptr & connection, const JsonNode & json) From 461bca73f35d789dfcf4452a0b85d25cfec8ceee Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 29 Dec 2023 00:31:53 +0200 Subject: [PATCH 24/98] Minor cleanup - move reinterpret_cast to jsonNode --- client/globalLobby/GlobalLobbyClient.cpp | 4 +--- lib/JsonNode.cpp | 4 ++++ lib/JsonNode.h | 1 + lobby/LobbyServer.cpp | 3 +-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index fd3ba1ac8..043efd9c8 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -57,9 +57,7 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptr(message.data()); - JsonNode json(payloadBegin, message.size()); + JsonNode json(message.data(), message.size()); if (json["type"].String() == "authentication") { diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index 1402ecd13..4b2353a7b 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -72,6 +72,10 @@ JsonNode::JsonNode(JsonType Type) setType(Type); } +JsonNode::JsonNode(const uint8_t *data, size_t datasize) + :JsonNode(reinterpret_cast(data), datasize) +{} + JsonNode::JsonNode(const char *data, size_t datasize) { JsonParser parser(data, datasize); diff --git a/lib/JsonNode.h b/lib/JsonNode.h index 47ae9a1a0..4c0dfc05d 100644 --- a/lib/JsonNode.h +++ b/lib/JsonNode.h @@ -51,6 +51,7 @@ public: JsonNode(JsonType Type = JsonType::DATA_NULL); //Create tree from Json-formatted input explicit JsonNode(const char * data, size_t datasize); + explicit JsonNode(const uint8_t * data, size_t datasize); //Create tree from JSON file explicit JsonNode(const JsonPath & fileURI); explicit JsonNode(const std::string & modName, const JsonPath & fileURI); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 67627b447..853025f44 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -129,8 +129,7 @@ void LobbyServer::onDisconnected(const std::shared_ptr & conn void LobbyServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) { // FIXME: find better approach - const char * payloadBegin = reinterpret_cast(message.data()); - JsonNode json(payloadBegin, message.size()); + JsonNode json(message.data(), message.size()); if (json["type"].String() == "sendChatMessage") return receiveSendChatMessage(connection, json); From 428a71087a5302cf8559aa498ef8b4a795a4a114 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 29 Dec 2023 13:09:56 +0200 Subject: [PATCH 25/98] Reorganization of lobby server project for future extension --- lobby/CMakeLists.txt | 5 ++ lobby/EntryPoint.cpp | 27 ++++++++++ lobby/LobbyDatabase.cpp | 91 +++++++++++++++++++++++++++++++++ lobby/LobbyDatabase.h | 83 +++++++++++++++++++++++++++++++ lobby/LobbyServer.cpp | 105 +++------------------------------------ lobby/LobbyServer.h | 34 +++---------- lobby/SQLiteConnection.h | 8 +++ 7 files changed, 228 insertions(+), 125 deletions(-) create mode 100644 lobby/EntryPoint.cpp create mode 100644 lobby/LobbyDatabase.cpp create mode 100644 lobby/LobbyDatabase.h diff --git a/lobby/CMakeLists.txt b/lobby/CMakeLists.txt index 60ce2ffba..6bd2d267e 100644 --- a/lobby/CMakeLists.txt +++ b/lobby/CMakeLists.txt @@ -1,11 +1,16 @@ set(lobby_SRCS StdInc.cpp + + EntryPoint.cpp + LobbyDatabase.cpp LobbyServer.cpp SQLiteConnection.cpp ) set(lobby_HEADERS StdInc.h + + LobbyDatabase.h LobbyServer.h SQLiteConnection.h ) diff --git a/lobby/EntryPoint.cpp b/lobby/EntryPoint.cpp new file mode 100644 index 000000000..4b1ce4cb2 --- /dev/null +++ b/lobby/EntryPoint.cpp @@ -0,0 +1,27 @@ +/* + * EntryPoint.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include "LobbyServer.h" + +static const std::string DATABASE_PATH = "/home/ivan/vcmi.db"; +static const int LISTENING_PORT = 30303; +//static const std::string SERVER_NAME = GameConstants::VCMI_VERSION + " (server)"; +//static const std::string SERVER_UUID = boost::uuids::to_string(boost::uuids::random_generator()()); + +int main(int argc, const char * argv[]) +{ + LobbyServer server(DATABASE_PATH); + + server.start(LISTENING_PORT); + server.run(); + + return 0; +} diff --git a/lobby/LobbyDatabase.cpp b/lobby/LobbyDatabase.cpp new file mode 100644 index 000000000..59712b4dd --- /dev/null +++ b/lobby/LobbyDatabase.cpp @@ -0,0 +1,91 @@ +/* + * LobbyServer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "LobbyDatabase.h" + +#include "SQLiteConnection.h" + +void LobbyDatabase::prepareStatements() +{ + static const std::string insertChatMessageText = R"( + INSERT INTO chatMessages(senderName, messageText) VALUES( ?, ?); + )"; + + static const std::string getRecentMessageHistoryText = R"( + SELECT senderName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',sendTime) AS secondsElapsed + FROM chatMessages + WHERE secondsElapsed < 60*60*24 + ORDER BY sendTime DESC + LIMIT 100 + )"; + + insertChatMessageStatement = database->prepare(insertChatMessageText); + getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText); +} + +void LobbyDatabase::createTableChatMessages() +{ + static const std::string statementText = R"( + CREATE TABLE IF NOT EXISTS chatMessages ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + senderName TEXT, + messageText TEXT, + sendTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL + ); + )"; + + auto statement = database->prepare(statementText); + statement->execute(); +} + +void LobbyDatabase::initializeDatabase() +{ + createTableChatMessages(); +} + +LobbyDatabase::~LobbyDatabase() = default; + +LobbyDatabase::LobbyDatabase(const std::string & databasePath) +{ + database = SQLiteInstance::open(databasePath, true); + + if (!database) + throw std::runtime_error("Failed to open SQLite database!"); + + initializeDatabase(); + prepareStatements(); +} + +void LobbyDatabase::insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomName, const std::string & messageText) +{ + insertChatMessageStatement->setBinds(sender, messageText); + insertChatMessageStatement->execute(); + insertChatMessageStatement->reset(); +} + +bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountName) +{ + return false; //TODO +} + +std::vector LobbyDatabase::getRecentMessageHistory() +{ + std::vector result; + + while(getRecentMessageHistoryStatement->execute()) + { + LobbyDatabase::ChatMessage message; + getRecentMessageHistoryStatement->getColumns(message.sender, message.messageText, message.age); + result.push_back(message); + } + getRecentMessageHistoryStatement->reset(); + + return result; +} diff --git a/lobby/LobbyDatabase.h b/lobby/LobbyDatabase.h new file mode 100644 index 000000000..bd1ccc52c --- /dev/null +++ b/lobby/LobbyDatabase.h @@ -0,0 +1,83 @@ +/* + * LobbyDatabase.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +class SQLiteInstance; +class SQLiteStatement; + +using SQLiteInstancePtr = std::unique_ptr; +using SQLiteStatementPtr = std::unique_ptr; + +class LobbyDatabase +{ + SQLiteInstancePtr database; + + SQLiteStatementPtr insertChatMessageStatement; + SQLiteStatementPtr insertAccountStatement; + SQLiteStatementPtr insertAccessCookieStatement; + SQLiteStatementPtr insertGameRoomStatement; + + SQLiteStatementPtr checkAccessCookieStatement; + SQLiteStatementPtr isPlayerInGameRoomStatement; + SQLiteStatementPtr isAccountNameAvailableStatement; + + SQLiteStatementPtr getRecentMessageHistoryStatement; + SQLiteStatementPtr setGameRoomStatusStatement; + SQLiteStatementPtr setGameRoomPlayerLimitStatement; + SQLiteStatementPtr insertPlayerIntoGameRoomStatement; + SQLiteStatementPtr removePlayerFromGameRoomStatement; + + void initializeDatabase(); + void prepareStatements(); + + void createTableChatMessages(); + void createTableGameRoomPlayers(); + void createTableGameRooms(); + void createTableAccounts(); + void createTableAccountCookies(); + +public: + struct GameRoom + { + std::string roomUUID; + std::string roomStatus; + std::chrono::seconds age; + uint32_t playersCount; + uint32_t playersLimit; + }; + + struct ChatMessage + { + std::string sender; + std::string messageText; + std::chrono::seconds age; + }; + + explicit LobbyDatabase(const std::string & databasePath); + ~LobbyDatabase(); + + void setGameRoomStatus(const std::string & roomUUID, const std::string & roomStatus); + void setGameRoomPlayerLimit(const std::string & roomUUID, uint32_t playerLimit); + + void insertPlayerIntoGameRoom(const std::string & accountName, const std::string & roomUUID); + void removePlayerFromGameRoom(const std::string & accountName, const std::string & roomUUID); + + void insertGameRoom(const std::string & roomUUID, const std::string & hostAccountName); + void insertAccount(const std::string & accountName); + void insertAccessCookie(const std::string & accountName, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime); + void insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomName, const std::string & messageText); + + std::vector getActiveGameRooms(); + std::vector getRecentMessageHistory(); + + bool checkAccessCookie(const std::string & accountName,const std::string & accessCookieUUID); + bool isPlayerInGameRoom(const std::string & accountName); + bool isAccountNameAvailable(const std::string & accountName); +}; diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 853025f44..543ba7fe5 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -10,93 +10,10 @@ #include "StdInc.h" #include "LobbyServer.h" -#include "SQLiteConnection.h" +#include "LobbyDatabase.h" #include "../lib/JsonNode.h" - -#include -#include - -static const std::string DATABASE_PATH = "/home/ivan/vcmi.db"; -static const int LISTENING_PORT = 30303; -//static const std::string SERVER_NAME = GameConstants::VCMI_VERSION + " (server)"; -//static const std::string SERVER_UUID = boost::uuids::to_string(boost::uuids::random_generator()()); - -void LobbyDatabase::prepareStatements() -{ - static const std::string insertChatMessageText = R"( - INSERT INTO chatMessages(senderName, messageText) VALUES( ?, ?); - )"; - - static const std::string getRecentMessageHistoryText = R"( - SELECT senderName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',sendTime) AS secondsElapsed - FROM chatMessages - WHERE secondsElapsed < 60*60*24 - ORDER BY sendTime DESC - LIMIT 100 - )"; - - insertChatMessageStatement = database->prepare(insertChatMessageText); - getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText); -} - -void LobbyDatabase::createTableChatMessages() -{ - static const std::string statementText = R"( - CREATE TABLE IF NOT EXISTS chatMessages ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - senderName TEXT, - messageText TEXT, - sendTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL - ); - )"; - - auto statement = database->prepare(statementText); - statement->execute(); -} - -void LobbyDatabase::initializeDatabase() -{ - createTableChatMessages(); -} - -LobbyDatabase::LobbyDatabase() -{ - database = SQLiteInstance::open(DATABASE_PATH, true); - - if (!database) - throw std::runtime_error("Failed to open SQLite database!"); - - initializeDatabase(); - prepareStatements(); -} - -void LobbyDatabase::insertChatMessage(const std::string & sender, const std::string & messageText) -{ - insertChatMessageStatement->setBinds(sender, messageText); - insertChatMessageStatement->execute(); - insertChatMessageStatement->reset(); -} - -bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountName) -{ - return false; //TODO -} - -std::vector LobbyDatabase::getRecentMessageHistory() -{ - std::vector result; - - while(getRecentMessageHistoryStatement->execute()) - { - LobbyDatabase::ChatMessage message; - getRecentMessageHistoryStatement->getColumns(message.sender, message.messageText, message.messageAgeSeconds); - result.push_back(message); - } - getRecentMessageHistoryStatement->reset(); - - return result; -} +#include "../lib/network/NetworkServer.h" void LobbyServer::sendMessage(const std::shared_ptr & target, const JsonNode & json) { @@ -149,7 +66,7 @@ void LobbyServer::receiveSendChatMessage(const std::shared_ptrinsertChatMessage(senderName, messageText); + database->insertChatMessage(senderName, "general", "english", messageText); JsonNode reply; reply["type"].String() = "chatMessage"; @@ -191,7 +108,7 @@ void LobbyServer::receiveAuthentication(const std::shared_ptr jsonEntry["messageText"].String() = message.messageText; jsonEntry["senderName"].String() = message.sender; - jsonEntry["ageSeconds"].Integer() = message.messageAgeSeconds; + jsonEntry["ageSeconds"].Integer() = message.age.count(); reply["messages"].Vector().push_back(jsonEntry); } @@ -215,8 +132,10 @@ void LobbyServer::receiveJoinGameRoom(const std::shared_ptr & // TODO: connection mode: direct or proxy } -LobbyServer::LobbyServer() - : database(new LobbyDatabase()) +LobbyServer::~LobbyServer() = default; + +LobbyServer::LobbyServer(const std::string & databasePath) + : database(new LobbyDatabase(databasePath)) , networkServer(new NetworkServer(*this)) { } @@ -230,11 +149,3 @@ void LobbyServer::run() { networkServer->run(); } - -int main(int argc, const char * argv[]) -{ - LobbyServer server; - - server.start(LISTENING_PORT); - server.run(); -} diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index 3c79a6b78..bd4081b74 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -9,38 +9,13 @@ */ #pragma once -#include "../lib/network/NetworkServer.h" +#include "../lib/network/NetworkListener.h" VCMI_LIB_NAMESPACE_BEGIN class JsonNode; VCMI_LIB_NAMESPACE_END -class SQLiteInstance; -class SQLiteStatement; - -class LobbyDatabase -{ - std::unique_ptr database; - std::unique_ptr insertChatMessageStatement; - std::unique_ptr getRecentMessageHistoryStatement; - - void initializeDatabase(); - void prepareStatements(); - void createTableChatMessages(); -public: - struct ChatMessage - { - std::string sender; - std::string messageText; - int messageAgeSeconds; - }; - - LobbyDatabase(); - - void insertChatMessage(const std::string & sender, const std::string & messageText); - std::vector getRecentMessageHistory(); - bool isPlayerInGameRoom(const std::string & accountName); -}; +class LobbyDatabase; class LobbyServer : public INetworkServerListener { @@ -54,6 +29,8 @@ class LobbyServer : public INetworkServerListener std::unique_ptr database; std::unique_ptr networkServer; + bool isAccountNameValid(const std::string & accountName); + void onNewConnection(const std::shared_ptr &) override; void onDisconnected(const std::shared_ptr &) override; void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; @@ -65,7 +42,8 @@ class LobbyServer : public INetworkServerListener void receiveAuthentication(const std::shared_ptr & connection, const JsonNode & json); void receiveJoinGameRoom(const std::shared_ptr & connection, const JsonNode & json); public: - LobbyServer(); + LobbyServer(const std::string & databasePath); + ~LobbyServer(); void start(uint16_t port); void run(); diff --git a/lobby/SQLiteConnection.h b/lobby/SQLiteConnection.h index 2cfae0f63..5eca8bf70 100644 --- a/lobby/SQLiteConnection.h +++ b/lobby/SQLiteConnection.h @@ -59,6 +59,14 @@ private: void getColumnSingle( size_t index, int64_t & value ); void getColumnSingle( size_t index, std::string & value ); + template + void getColumnSingle( size_t index, std::chrono::duration & value ) + { + int64_t durationValue = 0; + getColumnSingle(index, durationValue); + value = std::chrono::duration(durationValue); + } + SQLiteStatement( SQLiteInstance & instance, sqlite3_stmt * statement ); template From 50c14522213aa72470f60754be4f6ed91fea4466 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 29 Dec 2023 13:19:04 +0200 Subject: [PATCH 26/98] Fix code style consistency in lobby server project --- lobby/LobbyDatabase.cpp | 2 +- lobby/LobbyDatabase.h | 2 +- lobby/LobbyServer.cpp | 21 +++-- lobby/LobbyServer.h | 1 + lobby/SQLiteConnection.cpp | 175 +++++++++++++++++-------------------- lobby/SQLiteConnection.h | 82 +++++++++-------- 6 files changed, 134 insertions(+), 149 deletions(-) diff --git a/lobby/LobbyDatabase.cpp b/lobby/LobbyDatabase.cpp index 59712b4dd..673f33048 100644 --- a/lobby/LobbyDatabase.cpp +++ b/lobby/LobbyDatabase.cpp @@ -56,7 +56,7 @@ LobbyDatabase::LobbyDatabase(const std::string & databasePath) { database = SQLiteInstance::open(databasePath, true); - if (!database) + if(!database) throw std::runtime_error("Failed to open SQLite database!"); initializeDatabase(); diff --git a/lobby/LobbyDatabase.h b/lobby/LobbyDatabase.h index bd1ccc52c..fccea96f2 100644 --- a/lobby/LobbyDatabase.h +++ b/lobby/LobbyDatabase.h @@ -77,7 +77,7 @@ public: std::vector getActiveGameRooms(); std::vector getRecentMessageHistory(); - bool checkAccessCookie(const std::string & accountName,const std::string & accessCookieUUID); + bool checkAccessCookie(const std::string & accountName, const std::string & accessCookieUUID); bool isPlayerInGameRoom(const std::string & accountName); bool isAccountNameAvailable(const std::string & accountName); }; diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 543ba7fe5..35c64aa39 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -21,7 +21,7 @@ void LobbyServer::sendMessage(const std::shared_ptr & target, std::string payloadString = json.toJson(true); // FIXME: find better approach - uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); + uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); uint8_t * payloadEnd = payloadBegin + payloadString.size(); std::vector payloadBuffer(payloadBegin, payloadEnd); @@ -35,8 +35,7 @@ void LobbyServer::onTimer() } void LobbyServer::onNewConnection(const std::shared_ptr & connection) -{ -} +{} void LobbyServer::onDisconnected(const std::shared_ptr & connection) { @@ -48,19 +47,19 @@ void LobbyServer::onPacketReceived(const std::shared_ptr & co // FIXME: find better approach JsonNode json(message.data(), message.size()); - if (json["type"].String() == "sendChatMessage") + if(json["type"].String() == "sendChatMessage") return receiveSendChatMessage(connection, json); - if (json["type"].String() == "authentication") + if(json["type"].String() == "authentication") return receiveAuthentication(connection, json); - if (json["type"].String() == "joinGameRoom") + if(json["type"].String() == "joinGameRoom") return receiveJoinGameRoom(connection, json); } void LobbyServer::receiveSendChatMessage(const std::shared_ptr & connection, const JsonNode & json) { - if (activeAccounts.count(connection) == 0) + if(activeAccounts.count(connection) == 0) return; // unauthenticated std::string senderName = activeAccounts[connection].accountName; @@ -73,7 +72,7 @@ void LobbyServer::receiveSendChatMessage(const std::shared_ptr JsonNode reply; reply["type"].String() = "chatHistory"; - for (auto const & message : boost::adaptors::reverse(history)) + for(const auto & message : boost::adaptors::reverse(history)) { JsonNode jsonEntry; @@ -119,12 +118,12 @@ void LobbyServer::receiveAuthentication(const std::shared_ptr void LobbyServer::receiveJoinGameRoom(const std::shared_ptr & connection, const JsonNode & json) { - if (activeAccounts.count(connection) == 0) + if(activeAccounts.count(connection) == 0) return; // unauthenticated std::string senderName = activeAccounts[connection].accountName; - if (database->isPlayerInGameRoom(senderName)) + if(database->isPlayerInGameRoom(senderName)) return; // only 1 room per player allowed // TODO: roomType: private, public diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index bd4081b74..c9f278349 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -41,6 +41,7 @@ class LobbyServer : public INetworkServerListener void receiveSendChatMessage(const std::shared_ptr & connection, const JsonNode & json); void receiveAuthentication(const std::shared_ptr & connection, const JsonNode & json); void receiveJoinGameRoom(const std::shared_ptr & connection, const JsonNode & json); + public: LobbyServer(const std::string & databasePath); ~LobbyServer(); diff --git a/lobby/SQLiteConnection.cpp b/lobby/SQLiteConnection.cpp index 16cffb950..40fd62d86 100644 --- a/lobby/SQLiteConnection.cpp +++ b/lobby/SQLiteConnection.cpp @@ -12,111 +12,113 @@ #include -static void on_sqlite_error( sqlite3 * connection, [[maybe_unused]] int result ) +[[noreturn]] static void handleSQLiteError(sqlite3 * connection) { - if ( result != SQLITE_OK ) - { - const char * message = sqlite3_errmsg( connection ); - printf( "sqlite error: %s\n", message ); - } - - assert( result == SQLITE_OK ); + const char * message = sqlite3_errmsg(connection); + throw std::runtime_error(std::string("SQLite error: ") + message); } -SQLiteStatement::SQLiteStatement( SQLiteInstance & instance, sqlite3_stmt * statement ): - m_instance( instance ), - m_statement( statement ) -{ } - -SQLiteStatement::~SQLiteStatement( ) -{ - int result = sqlite3_finalize( m_statement ); - on_sqlite_error( m_instance.m_connection, result ); +static void checkSQLiteError(sqlite3 * connection, int result) +{ + if(result != SQLITE_OK) + handleSQLiteError(connection); } -bool SQLiteStatement::execute( ) -{ - int result = sqlite3_step( m_statement ); +SQLiteStatement::SQLiteStatement(SQLiteInstance & instance, sqlite3_stmt * statement) + : m_instance(instance) + , m_statement(statement) +{ +} - switch ( result ) +SQLiteStatement::~SQLiteStatement() +{ + int result = sqlite3_finalize(m_statement); + checkSQLiteError(m_instance.m_connection, result); +} + +bool SQLiteStatement::execute() +{ + int result = sqlite3_step(m_statement); + + switch(result) { case SQLITE_DONE: return false; case SQLITE_ROW: return true; default: - on_sqlite_error( m_instance.m_connection, result ); + checkSQLiteError(m_instance.m_connection, result); return false; } } -void SQLiteStatement::reset( ) +void SQLiteStatement::reset() { - int result = sqlite3_reset( m_statement ); - on_sqlite_error( m_instance.m_connection, result ); + int result = sqlite3_reset(m_statement); + checkSQLiteError(m_instance.m_connection, result); } void SQLiteStatement::clear() { int result = sqlite3_clear_bindings(m_statement); - on_sqlite_error(m_instance.m_connection, result); + checkSQLiteError(m_instance.m_connection, result); } -void SQLiteStatement::setBindSingle( size_t index, double const & value ) +void SQLiteStatement::setBindSingle(size_t index, const double & value) { - int result = sqlite3_bind_double( m_statement, static_cast(index), value ); - on_sqlite_error( m_instance.m_connection, result ); + int result = sqlite3_bind_double(m_statement, static_cast(index), value); + checkSQLiteError(m_instance.m_connection, result); } -void SQLiteStatement::setBindSingle(size_t index, uint8_t const & value) +void SQLiteStatement::setBindSingle(size_t index, const uint8_t & value) { int result = sqlite3_bind_int(m_statement, static_cast(index), value); - on_sqlite_error(m_instance.m_connection, result); + checkSQLiteError(m_instance.m_connection, result); } -void SQLiteStatement::setBindSingle(size_t index, uint16_t const & value) +void SQLiteStatement::setBindSingle(size_t index, const uint16_t & value) { int result = sqlite3_bind_int(m_statement, static_cast(index), value); - on_sqlite_error(m_instance.m_connection, result); + checkSQLiteError(m_instance.m_connection, result); } -void SQLiteStatement::setBindSingle(size_t index, uint32_t const & value) +void SQLiteStatement::setBindSingle(size_t index, const uint32_t & value) { int result = sqlite3_bind_int(m_statement, static_cast(index), value); - on_sqlite_error(m_instance.m_connection, result); + checkSQLiteError(m_instance.m_connection, result); } -void SQLiteStatement::setBindSingle( size_t index, int32_t const & value ) +void SQLiteStatement::setBindSingle(size_t index, const int32_t & value) { - int result = sqlite3_bind_int( m_statement, static_cast( index ), value ); - on_sqlite_error( m_instance.m_connection, result ); + int result = sqlite3_bind_int(m_statement, static_cast(index), value); + checkSQLiteError(m_instance.m_connection, result); } -void SQLiteStatement::setBindSingle( size_t index, int64_t const & value ) +void SQLiteStatement::setBindSingle(size_t index, const int64_t & value) { - int result = sqlite3_bind_int64( m_statement, static_cast( index ), value ); - on_sqlite_error( m_instance.m_connection, result ); + int result = sqlite3_bind_int64(m_statement, static_cast(index), value); + checkSQLiteError(m_instance.m_connection, result); } -void SQLiteStatement::setBindSingle( size_t index, std::string const & value ) +void SQLiteStatement::setBindSingle(size_t index, const std::string & value) { - int result = sqlite3_bind_text( m_statement, static_cast( index ), value.data(), static_cast( value.size() ), SQLITE_STATIC ); - on_sqlite_error( m_instance.m_connection, result ); + int result = sqlite3_bind_text(m_statement, static_cast(index), value.data(), static_cast(value.size()), SQLITE_STATIC); + checkSQLiteError(m_instance.m_connection, result); } -void SQLiteStatement::setBindSingle( size_t index, char const * value ) +void SQLiteStatement::setBindSingle(size_t index, const char * value) { - int result = sqlite3_bind_text( m_statement, static_cast( index ), value, -1, SQLITE_STATIC ); - on_sqlite_error( m_instance.m_connection, result ); + int result = sqlite3_bind_text(m_statement, static_cast(index), value, -1, SQLITE_STATIC); + checkSQLiteError(m_instance.m_connection, result); } -void SQLiteStatement::getColumnSingle( size_t index, double & value ) +void SQLiteStatement::getColumnSingle(size_t index, double & value) { - value = sqlite3_column_double( m_statement, static_cast( index ) ); + value = sqlite3_column_double(m_statement, static_cast(index)); } -void SQLiteStatement::getColumnSingle( size_t index, uint8_t & value ) +void SQLiteStatement::getColumnSingle(size_t index, uint8_t & value) { - value = static_cast(sqlite3_column_int( m_statement, static_cast( index ) )); + value = static_cast(sqlite3_column_int(m_statement, static_cast(index))); } void SQLiteStatement::getColumnSingle(size_t index, uint16_t & value) @@ -124,9 +126,9 @@ void SQLiteStatement::getColumnSingle(size_t index, uint16_t & value) value = static_cast(sqlite3_column_int(m_statement, static_cast(index))); } -void SQLiteStatement::getColumnSingle( size_t index, int32_t & value ) +void SQLiteStatement::getColumnSingle(size_t index, int32_t & value) { - value = sqlite3_column_int( m_statement, static_cast( index ) ); + value = sqlite3_column_int(m_statement, static_cast(index)); } void SQLiteStatement::getColumnSingle(size_t index, uint32_t & value) @@ -134,65 +136,50 @@ void SQLiteStatement::getColumnSingle(size_t index, uint32_t & value) value = sqlite3_column_int(m_statement, static_cast(index)); } -void SQLiteStatement::getColumnSingle( size_t index, int64_t & value ) +void SQLiteStatement::getColumnSingle(size_t index, int64_t & value) { - value = sqlite3_column_int64( m_statement, static_cast( index ) ); + value = sqlite3_column_int64(m_statement, static_cast(index)); } -void SQLiteStatement::getColumnSingle( size_t index, std::string & value ) +void SQLiteStatement::getColumnSingle(size_t index, std::string & value) { - auto value_raw = sqlite3_column_text(m_statement, static_cast(index)); - value = reinterpret_cast( value_raw ); + const auto * value_raw = sqlite3_column_text(m_statement, static_cast(index)); + value = reinterpret_cast(value_raw); } -void SQLiteStatement::getColumnBlob(size_t index, std::byte * value, [[maybe_unused]] size_t capacity) -{ - auto * blob_data = sqlite3_column_blob(m_statement, static_cast(index)); - size_t blob_size = sqlite3_column_bytes(m_statement, static_cast(index)); - - assert(blob_size < capacity); - - std::copy_n(static_cast(blob_data), blob_size, value); - value[blob_size] = std::byte(0); -} - -SQLiteInstancePtr SQLiteInstance::open( std::string const & db_path, bool allow_write) +SQLiteInstancePtr SQLiteInstance::open(const std::string & db_path, bool allow_write) { int flags = allow_write ? (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) : SQLITE_OPEN_READONLY; sqlite3 * connection; - int result = sqlite3_open_v2( db_path.c_str( ), &connection, flags, nullptr ); + int result = sqlite3_open_v2(db_path.c_str(), &connection, flags, nullptr); - on_sqlite_error( connection, result ); + if(result == SQLITE_OK) + return SQLiteInstancePtr(new SQLiteInstance(connection)); - assert(result == SQLITE_OK); - - if ( result == SQLITE_OK ) - return SQLiteInstancePtr( new SQLiteInstance( connection ) ); - - sqlite3_close( connection ); - return SQLiteInstancePtr( ); + sqlite3_close(connection); + handleSQLiteError(connection); } -SQLiteInstance::SQLiteInstance( sqlite3 * connection ): - m_connection( connection ) -{ } - -SQLiteInstance::~SQLiteInstance( ) +SQLiteInstance::SQLiteInstance(sqlite3 * connection) + : m_connection(connection) { - int result = sqlite3_close( m_connection ); - on_sqlite_error( m_connection, result ); } -SQLiteStatementPtr SQLiteInstance::prepare( std::string const & sql_text ) +SQLiteInstance::~SQLiteInstance() +{ + int result = sqlite3_close(m_connection); + checkSQLiteError(m_connection, result); +} + +SQLiteStatementPtr SQLiteInstance::prepare(const std::string & sql_text) { sqlite3_stmt * statement; - int result = sqlite3_prepare_v2( m_connection, sql_text.data(), static_cast( sql_text.size()), &statement, nullptr ); - - on_sqlite_error( m_connection, result ); + int result = sqlite3_prepare_v2(m_connection, sql_text.data(), static_cast(sql_text.size()), &statement, nullptr); - if ( result == SQLITE_OK ) - return SQLiteStatementPtr( new SQLiteStatement( *this, statement ) ); - sqlite3_finalize( statement ); - return SQLiteStatementPtr( ); + if(result == SQLITE_OK) + return SQLiteStatementPtr(new SQLiteStatement(*this, statement)); + + sqlite3_finalize(statement); + handleSQLiteError(m_connection); } diff --git a/lobby/SQLiteConnection.h b/lobby/SQLiteConnection.h index 5eca8bf70..d80c426b7 100644 --- a/lobby/SQLiteConnection.h +++ b/lobby/SQLiteConnection.h @@ -15,76 +15,74 @@ typedef struct sqlite3_stmt sqlite3_stmt; class SQLiteInstance; class SQLiteStatement; -using SQLiteInstancePtr = std::unique_ptr< SQLiteInstance >; -using SQLiteStatementPtr = std::unique_ptr< SQLiteStatement >; +using SQLiteInstancePtr = std::unique_ptr; +using SQLiteStatementPtr = std::unique_ptr; class SQLiteStatement : boost::noncopyable { public: friend class SQLiteInstance; - bool execute( ); - void reset( ); - void clear( ); + bool execute(); + void reset(); + void clear(); ~SQLiteStatement(); - template - void setBinds( Args const & ... args ) + template + void setBinds(const Args &... args) { - setBindSingle( 1, args... ); // The leftmost SQL parameter has an index of 1 + setBindSingle(1, args...); // The leftmost SQL parameter has an index of 1 } - template - void getColumns( Args & ... args ) + template + void getColumns(Args &... args) { - getColumnSingle( 0, args... ); // The leftmost column of the result set has the index 0 + getColumnSingle(0, args...); // The leftmost column of the result set has the index 0 } private: - void setBindSingle( size_t index, double const & value ); - void setBindSingle( size_t index, uint8_t const & value ); - void setBindSingle( size_t index, uint16_t const & value ); - void setBindSingle( size_t index, uint32_t const & value ); - void setBindSingle( size_t index, int32_t const & value ); - void setBindSingle( size_t index, int64_t const & value ); - void setBindSingle( size_t index, std::string const & value ); - void setBindSingle( size_t index, char const * value ); + void setBindSingle(size_t index, const double & value); + void setBindSingle(size_t index, const uint8_t & value); + void setBindSingle(size_t index, const uint16_t & value); + void setBindSingle(size_t index, const uint32_t & value); + void setBindSingle(size_t index, const int32_t & value); + void setBindSingle(size_t index, const int64_t & value); + void setBindSingle(size_t index, const std::string & value); + void setBindSingle(size_t index, const char * value); - void getColumnSingle( size_t index, double & value ); - void getColumnSingle( size_t index, uint8_t & value ); - void getColumnSingle( size_t index, uint16_t & value ); - void getColumnSingle( size_t index, uint32_t & value ); - void getColumnSingle( size_t index, int32_t & value ); - void getColumnSingle( size_t index, int64_t & value ); - void getColumnSingle( size_t index, std::string & value ); + void getColumnSingle(size_t index, double & value); + void getColumnSingle(size_t index, uint8_t & value); + void getColumnSingle(size_t index, uint16_t & value); + void getColumnSingle(size_t index, uint32_t & value); + void getColumnSingle(size_t index, int32_t & value); + void getColumnSingle(size_t index, int64_t & value); + void getColumnSingle(size_t index, std::string & value); template - void getColumnSingle( size_t index, std::chrono::duration & value ) + void getColumnSingle(size_t index, std::chrono::duration & value) { int64_t durationValue = 0; getColumnSingle(index, durationValue); value = std::chrono::duration(durationValue); } - SQLiteStatement( SQLiteInstance & instance, sqlite3_stmt * statement ); + SQLiteStatement(SQLiteInstance & instance, sqlite3_stmt * statement); - template - void setBindSingle( size_t index, T const & arg, Args const & ... args ) + template + void setBindSingle(size_t index, T const & arg, const Args &... args) { - setBindSingle( index, arg ); - setBindSingle( index + 1, args... ); + setBindSingle(index, arg); + setBindSingle(index + 1, args...); } - template - void getColumnSingle( size_t index, T & arg, Args & ... args ) + template + void getColumnSingle(size_t index, T & arg, Args &... args) { - getColumnSingle( index, arg ); - getColumnSingle( index + 1, args... ); + getColumnSingle(index, arg); + getColumnSingle(index + 1, args...); } - void getColumnBlob(size_t index, std::byte * value, size_t capacity); - SQLiteInstance & m_instance; sqlite3_stmt * m_statement; }; @@ -94,14 +92,14 @@ class SQLiteInstance : boost::noncopyable public: friend class SQLiteStatement; - static SQLiteInstancePtr open(std::string const & db_path, bool allow_write ); + static SQLiteInstancePtr open(const std::string & db_path, bool allow_write); - ~SQLiteInstance( ); + ~SQLiteInstance(); - SQLiteStatementPtr prepare( std::string const & statement ); + SQLiteStatementPtr prepare(const std::string & statement); private: - SQLiteInstance( sqlite3 * connection ); + SQLiteInstance(sqlite3 * connection); sqlite3 * m_connection; }; From 4271fb3c95ba1f1fe188748ee0d299f8c7d9c85f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 30 Dec 2023 00:41:16 +0200 Subject: [PATCH 27/98] Extension of lobby server functionality Support for: - listing of active players - listing of active rooms - joining and leaving rooms - placeholder support for multiple chat rooms - proxy connections - invites into private rooms (only lobby server side for now, client and match server need work) --- Mods/vcmi/config/vcmi/english.json | 2 +- lib/network/NetworkListener.h | 12 +- lobby/CMakeLists.txt | 1 + lobby/EntryPoint.cpp | 9 +- lobby/LobbyDatabase.cpp | 393 +++++++++++++++++++-- lobby/LobbyDatabase.h | 95 +++--- lobby/LobbyDefines.h | 56 +++ lobby/LobbyServer.cpp | 530 +++++++++++++++++++++++++---- lobby/LobbyServer.h | 70 +++- lobby/SQLiteConnection.cpp | 13 +- lobby/SQLiteConnection.h | 12 +- 11 files changed, 1031 insertions(+), 162 deletions(-) create mode 100644 lobby/LobbyDefines.h diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 0cc354008..1431dab4d 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -75,7 +75,7 @@ "vcmi.lobby.login.title" : "VCMI Lobby", "vcmi.lobby.login.username" : "Username:", "vcmi.lobby.login.connecting" : "Connecting...", -// "vcmi.lobby.login.connectionFailed" : "Connection failed: %s", + "vcmi.lobby.login.error" : "Connection error: %s", "vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.", "vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.", diff --git a/lib/network/NetworkListener.h b/lib/network/NetworkListener.h index c9a5e6202..f1ee7885d 100644 --- a/lib/network/NetworkListener.h +++ b/lib/network/NetworkListener.h @@ -15,6 +15,9 @@ class NetworkConnection; class NetworkServer; class NetworkClient; +using NetworkConnectionPtr = std::shared_ptr; +using NetworkConnectionWeakPtr = std::weak_ptr; + class DLL_LINKAGE INetworkConnectionListener { friend class NetworkConnection; @@ -22,7 +25,8 @@ protected: virtual void onDisconnected(const std::shared_ptr & connection) = 0; virtual void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) = 0; - ~INetworkConnectionListener() = default; +public: + virtual ~INetworkConnectionListener() = default; }; class DLL_LINKAGE INetworkServerListener : public INetworkConnectionListener @@ -32,7 +36,8 @@ protected: virtual void onNewConnection(const std::shared_ptr &) = 0; virtual void onTimer() = 0; - ~INetworkServerListener() = default; +public: + virtual ~INetworkServerListener() = default; }; class DLL_LINKAGE INetworkClientListener : public INetworkConnectionListener @@ -43,7 +48,8 @@ protected: virtual void onConnectionEstablished(const std::shared_ptr &) = 0; virtual void onTimer() = 0; - ~INetworkClientListener() = default; +public: + virtual ~INetworkClientListener() = default; }; diff --git a/lobby/CMakeLists.txt b/lobby/CMakeLists.txt index 6bd2d267e..b4169ab09 100644 --- a/lobby/CMakeLists.txt +++ b/lobby/CMakeLists.txt @@ -11,6 +11,7 @@ set(lobby_HEADERS StdInc.h LobbyDatabase.h + LobbyDefines.h LobbyServer.h SQLiteConnection.h ) diff --git a/lobby/EntryPoint.cpp b/lobby/EntryPoint.cpp index 4b1ce4cb2..de4d3049e 100644 --- a/lobby/EntryPoint.cpp +++ b/lobby/EntryPoint.cpp @@ -11,14 +11,15 @@ #include "LobbyServer.h" -static const std::string DATABASE_PATH = "/home/ivan/vcmi.db"; +#include "../lib/VCMIDirs.h" + static const int LISTENING_PORT = 30303; -//static const std::string SERVER_NAME = GameConstants::VCMI_VERSION + " (server)"; -//static const std::string SERVER_UUID = boost::uuids::to_string(boost::uuids::random_generator()()); int main(int argc, const char * argv[]) { - LobbyServer server(DATABASE_PATH); + auto databasePath = VCMIDirs::get().userDataPath() / "vcmiLobby.db"; + + LobbyServer server(databasePath); server.start(LISTENING_PORT); server.run(); diff --git a/lobby/LobbyDatabase.cpp b/lobby/LobbyDatabase.cpp index 673f33048..e6c5b6158 100644 --- a/lobby/LobbyDatabase.cpp +++ b/lobby/LobbyDatabase.cpp @@ -12,76 +12,256 @@ #include "SQLiteConnection.h" +void LobbyDatabase::createTables() +{ + static const std::string createChatMessages = R"( + CREATE TABLE IF NOT EXISTS chatMessages ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + senderName TEXT, + roomType TEXT, + messageText TEXT, + creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL + ); + )"; + + static const std::string createTableGameRooms = R"( + CREATE TABLE IF NOT EXISTS gameRooms ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + roomID TEXT, + hostAccountID TEXT, + status INTEGER NOT NULL DEFAULT 0, + playerLimit INTEGER NOT NULL, + creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL + ); + )"; + + static const std::string createTableGameRoomPlayers = R"( + CREATE TABLE IF NOT EXISTS gameRoomPlayers ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + roomID TEXT, + accountID TEXT + ); + )"; + + static const std::string createTableAccounts = R"( + CREATE TABLE IF NOT EXISTS accounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + accountID TEXT, + displayName TEXT, + online INTEGER NOT NULL, + lastLoginTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL + creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL + ); + )"; + + static const std::string createTableAccountCookies = R"( + CREATE TABLE IF NOT EXISTS accountCookies ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + accountID TEXT, + cookieUUID TEXT, + creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL + ); + )"; + + static const std::string createTableGameRoomInvites = R"( + CREATE TABLE IF NOT EXISTS gameRoomInvites ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + roomID TEXT, + accountID TEXT + ); + )"; + + database->prepare(createChatMessages)->execute(); + database->prepare(createTableGameRoomPlayers)->execute(); + database->prepare(createTableGameRooms)->execute(); + database->prepare(createTableAccounts)->execute(); + database->prepare(createTableAccountCookies)->execute(); + database->prepare(createTableGameRoomInvites)->execute(); +} + void LobbyDatabase::prepareStatements() { + // INSERT INTO + static const std::string insertChatMessageText = R"( INSERT INTO chatMessages(senderName, messageText) VALUES( ?, ?); )"; + static const std::string insertAccountText = R"( + INSERT INTO accounts(accountID, displayName, online) VALUES(?,?,0); + )"; + + static const std::string insertAccessCookieText = R"( + INSERT INTO accountCookies(accountID, cookieUUID) VALUES(?,?); + )"; + + static const std::string insertGameRoomText = R"( + INSERT INTO gameRooms(roomID, hostAccountID, status, playerLimit) VALUES(?, ?, 'empty', 8); + )"; + + static const std::string insertGameRoomPlayersText = R"( + INSERT INTO gameRoomPlayers(roomID, accountID) VALUES(?,?); + )"; + + static const std::string insertGameRoomInvitesText = R"( + INSERT INTO gameRoomInvites(roomID, accountID) VALUES(?,?); + )"; + + // DELETE FROM + + static const std::string deleteGameRoomPlayersText = R"( + DELETE FROM gameRoomPlayers WHERE gameRoomID = ? AND accountID = ? + )"; + + static const std::string deleteGameRoomInvitesText = R"( + DELETE FROM gameRoomInvites WHERE gameRoomID = ? AND accountID = ? + )"; + + // UPDATE + + static const std::string setGameRoomStatusText = R"( + UPDATE gameRooms + SET status = ? + WHERE roomID = ? + )"; + + static const std::string setGameRoomPlayerLimitText = R"( + UPDATE gameRooms + SET playerLimit = ? + WHERE roomID = ? + )"; + + // SELECT FROM + static const std::string getRecentMessageHistoryText = R"( - SELECT senderName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',sendTime) AS secondsElapsed + SELECT senderName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',creationTime) AS secondsElapsed FROM chatMessages - WHERE secondsElapsed < 60*60*24 - ORDER BY sendTime DESC + WHERE secondsElapsed < 60*60*18 + ORDER BY creationTime DESC LIMIT 100 )"; - insertChatMessageStatement = database->prepare(insertChatMessageText); - getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText); -} - -void LobbyDatabase::createTableChatMessages() -{ - static const std::string statementText = R"( - CREATE TABLE IF NOT EXISTS chatMessages ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - senderName TEXT, - messageText TEXT, - sendTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL - ); + static const std::string getIdleGameRoomText = R"( + SELECT roomID + FROM gameRooms + WHERE hostAccountID = ? AND status = 'idle' + LIMIT 1 )"; - auto statement = database->prepare(statementText); - statement->execute(); -} + static const std::string getAccountGameRoomText = R"( + SELECT roomID + FROM gameRoomPlayers grp + LEFT JOIN gameRooms gr ON gr.roomID = grp.roomID + WHERE accountID = ? AND status IN ('public', 'private', 'busy') + LIMIT 1 + )"; -void LobbyDatabase::initializeDatabase() -{ - createTableChatMessages(); + static const std::string getActiveAccountsText = R"( + SELECT accountID, displayName + FROM accounts + WHERE online <> 1 + )"; + + static const std::string isAccountCookieValidText = R"( + SELECT COUNT(accountID) + FROM accountCookies + WHERE accountID = ? AND cookieUUID = ? AND strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',creationTime) < ? + )"; + + static const std::string isGameRoomCookieValidText = R"( + SELECT COUNT(roomID) + FROM gameRooms + LEFT JOIN accountCookies ON accountCookies.accountID = gameRooms.hostAccountID + WHERE roomID = ? AND cookieUUID = ? AND strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',creationTime) < ? + )"; + + static const std::string isPlayerInGameRoomText = R"( + SELECT COUNT(accountID) + FROM gameRoomPlayers + WHERE accountID = ? AND roomID = ? + )"; + + static const std::string isPlayerInAnyGameRoomText = R"( + SELECT COUNT(accountID) + FROM gameRoomPlayers + WHERE accountID = ? + )"; + + static const std::string isAccountExistsText = R"( + SELECT COUNT(accountID) + FROM accounts + WHERE accountID = ? + )"; + + insertChatMessageStatement = database->prepare(insertChatMessageText); + insertAccountStatement = database->prepare(insertAccountText); + insertAccessCookieStatement = database->prepare(insertAccessCookieText); + insertGameRoomStatement = database->prepare(insertGameRoomText); + insertGameRoomPlayersStatement = database->prepare(insertGameRoomPlayersText); + insertGameRoomInvitesStatement = database->prepare(insertGameRoomInvitesText); + + deleteGameRoomPlayersStatement = database->prepare(deleteGameRoomPlayersText); + deleteGameRoomInvitesStatement = database->prepare(deleteGameRoomInvitesText); + + setGameRoomStatusStatement = database->prepare(setGameRoomStatusText); + setGameRoomPlayerLimitStatement = database->prepare(setGameRoomPlayerLimitText); + + getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText); + getIdleGameRoomStatement = database->prepare(getIdleGameRoomText); + getAccountGameRoomStatement = database->prepare(getAccountGameRoomText); + getActiveAccountsStatement = database->prepare(getActiveAccountsText); + + isAccountCookieValidStatement = database->prepare(isAccountCookieValidText); + isPlayerInGameRoomStatement = database->prepare(isPlayerInGameRoomText); + isPlayerInAnyGameRoomStatement = database->prepare(isPlayerInAnyGameRoomText); + isAccountExistsStatement = database->prepare(isAccountExistsText); } LobbyDatabase::~LobbyDatabase() = default; -LobbyDatabase::LobbyDatabase(const std::string & databasePath) +LobbyDatabase::LobbyDatabase(const boost::filesystem::path & databasePath) { database = SQLiteInstance::open(databasePath, true); - - if(!database) - throw std::runtime_error("Failed to open SQLite database!"); - - initializeDatabase(); + createTables(); prepareStatements(); } void LobbyDatabase::insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomName, const std::string & messageText) { - insertChatMessageStatement->setBinds(sender, messageText); - insertChatMessageStatement->execute(); - insertChatMessageStatement->reset(); + insertChatMessageStatement->executeOnce(sender, messageText); } -bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountName) +bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountID) { - return false; //TODO + bool result = false; + + isPlayerInAnyGameRoomStatement->setBinds(accountID); + if (isPlayerInAnyGameRoomStatement->execute()) + isPlayerInAnyGameRoomStatement->getColumns(result); + isPlayerInAnyGameRoomStatement->reset(); + + return result; } -std::vector LobbyDatabase::getRecentMessageHistory() +bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountID, const std::string & roomID) { - std::vector result; + bool result = false; + + isPlayerInGameRoomStatement->setBinds(accountID, roomID); + if (isPlayerInGameRoomStatement->execute()) + isPlayerInGameRoomStatement->getColumns(result); + isPlayerInGameRoomStatement->reset(); + + return result; +} + +std::vector LobbyDatabase::getRecentMessageHistory() +{ + std::vector result; while(getRecentMessageHistoryStatement->execute()) { - LobbyDatabase::ChatMessage message; + LobbyChatMessage message; getRecentMessageHistoryStatement->getColumns(message.sender, message.messageText, message.age); result.push_back(message); } @@ -89,3 +269,144 @@ std::vector LobbyDatabase::getRecentMessageHistory() return result; } + +void LobbyDatabase::setGameRoomStatus(const std::string & roomID, LobbyRoomState roomStatus) +{ + setGameRoomStatusStatement->executeOnce(vstd::to_underlying(roomStatus), roomID); +} + +void LobbyDatabase::setGameRoomPlayerLimit(const std::string & roomID, uint32_t playerLimit) +{ + setGameRoomPlayerLimitStatement->executeOnce(playerLimit, roomID); +} + +void LobbyDatabase::insertPlayerIntoGameRoom(const std::string & accountID, const std::string & roomID) +{ + insertGameRoomPlayersStatement->executeOnce(roomID, accountID); +} + +void LobbyDatabase::deletePlayerFromGameRoom(const std::string & accountID, const std::string & roomID) +{ + deleteGameRoomPlayersStatement->executeOnce(roomID, accountID); +} + +void LobbyDatabase::deleteGameRoomInvite(const std::string & targetAccountID, const std::string & roomID) +{ + deleteGameRoomInvitesStatement->executeOnce(roomID, targetAccountID); +} + +void LobbyDatabase::insertGameRoomInvite(const std::string & targetAccountID, const std::string & roomID) +{ + insertGameRoomInvitesStatement->executeOnce(roomID, targetAccountID); +} + +void LobbyDatabase::insertGameRoom(const std::string & roomID, const std::string & hostAccountID) +{ + insertGameRoomStatement->executeOnce(roomID, hostAccountID); +} + +void LobbyDatabase::insertAccount(const std::string & accountID, const std::string & displayName) +{ + insertAccountStatement->executeOnce(accountID, displayName); +} + +void LobbyDatabase::insertAccessCookie(const std::string & accountID, const std::string & accessCookieUUID) +{ + insertAccessCookieStatement->executeOnce(accountID, accessCookieUUID); +} + +void LobbyDatabase::updateAccessCookie(const std::string & accountID, const std::string & accessCookieUUID) +{ + +} + +void LobbyDatabase::updateAccountLoginTime(const std::string & accountID) +{ + +} + +void LobbyDatabase::updateActiveAccount(const std::string & accountID, bool isActive) +{ + +} + +std::string LobbyDatabase::getAccountDisplayName(const std::string & accountID) +{ + return {}; +} + +LobbyCookieStatus LobbyDatabase::getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime) +{ + return {}; +} + +LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime) +{ + return {}; +} + +LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & accountID, std::chrono::seconds cookieLifetime) +{ + return {}; +} + +LobbyInviteStatus LobbyDatabase::getAccountInviteStatus(const std::string & accountID, const std::string & roomID) +{ + return {}; +} + +LobbyRoomState LobbyDatabase::getGameRoomStatus(const std::string & roomID) +{ + return {}; +} + +uint32_t LobbyDatabase::getGameRoomFreeSlots(const std::string & roomID) +{ + return 0; +} + +bool LobbyDatabase::isAccountExists(const std::string & accountID) +{ + return false; +} + +std::vector LobbyDatabase::getActiveGameRooms() +{ + return {}; +} + +std::vector LobbyDatabase::getActiveAccounts() +{ + std::vector result; + + while(getActiveAccountsStatement->execute()) + { + LobbyAccount entry; + getActiveAccountsStatement->getColumns(entry.accountID, entry.displayName); + result.push_back(entry); + } + getActiveAccountsStatement->reset(); + return result; +} + +std::string LobbyDatabase::getIdleGameRoom(const std::string & hostAccountID) +{ + std::string result; + + if (getIdleGameRoomStatement->execute()) + getIdleGameRoomStatement->getColumns(result); + + getIdleGameRoomStatement->reset(); + return result; +} + +std::string LobbyDatabase::getAccountGameRoom(const std::string & accountID) +{ + std::string result; + + if (getAccountGameRoomStatement->execute()) + getAccountGameRoomStatement->getColumns(result); + + getAccountGameRoomStatement->reset(); + return result; +} diff --git a/lobby/LobbyDatabase.h b/lobby/LobbyDatabase.h index fccea96f2..4bc172ea0 100644 --- a/lobby/LobbyDatabase.h +++ b/lobby/LobbyDatabase.h @@ -9,6 +9,8 @@ */ #pragma once +#include "LobbyDefines.h" + class SQLiteInstance; class SQLiteStatement; @@ -23,61 +25,68 @@ class LobbyDatabase SQLiteStatementPtr insertAccountStatement; SQLiteStatementPtr insertAccessCookieStatement; SQLiteStatementPtr insertGameRoomStatement; + SQLiteStatementPtr insertGameRoomPlayersStatement; + SQLiteStatementPtr insertGameRoomInvitesStatement; - SQLiteStatementPtr checkAccessCookieStatement; - SQLiteStatementPtr isPlayerInGameRoomStatement; - SQLiteStatementPtr isAccountNameAvailableStatement; + SQLiteStatementPtr deleteGameRoomPlayersStatement; + SQLiteStatementPtr deleteGameRoomInvitesStatement; - SQLiteStatementPtr getRecentMessageHistoryStatement; SQLiteStatementPtr setGameRoomStatusStatement; SQLiteStatementPtr setGameRoomPlayerLimitStatement; - SQLiteStatementPtr insertPlayerIntoGameRoomStatement; - SQLiteStatementPtr removePlayerFromGameRoomStatement; - void initializeDatabase(); + SQLiteStatementPtr getRecentMessageHistoryStatement; + SQLiteStatementPtr getIdleGameRoomStatement; + SQLiteStatementPtr getAccountGameRoomStatement; + SQLiteStatementPtr getActiveAccountsStatement; + + SQLiteStatementPtr isAccountCookieValidStatement; + SQLiteStatementPtr isGameRoomCookieValidStatement; + SQLiteStatementPtr isPlayerInGameRoomStatement; + SQLiteStatementPtr isPlayerInAnyGameRoomStatement; + SQLiteStatementPtr isAccountExistsStatement; + void prepareStatements(); - - void createTableChatMessages(); - void createTableGameRoomPlayers(); - void createTableGameRooms(); - void createTableAccounts(); - void createTableAccountCookies(); + void createTables(); public: - struct GameRoom - { - std::string roomUUID; - std::string roomStatus; - std::chrono::seconds age; - uint32_t playersCount; - uint32_t playersLimit; - }; - - struct ChatMessage - { - std::string sender; - std::string messageText; - std::chrono::seconds age; - }; - - explicit LobbyDatabase(const std::string & databasePath); + explicit LobbyDatabase(const boost::filesystem::path & databasePath); ~LobbyDatabase(); - void setGameRoomStatus(const std::string & roomUUID, const std::string & roomStatus); - void setGameRoomPlayerLimit(const std::string & roomUUID, uint32_t playerLimit); + void setGameRoomStatus(const std::string & roomID, LobbyRoomState roomStatus); + void setGameRoomPlayerLimit(const std::string & roomID, uint32_t playerLimit); - void insertPlayerIntoGameRoom(const std::string & accountName, const std::string & roomUUID); - void removePlayerFromGameRoom(const std::string & accountName, const std::string & roomUUID); + void insertPlayerIntoGameRoom(const std::string & accountID, const std::string & roomID); + void deletePlayerFromGameRoom(const std::string & accountID, const std::string & roomID); - void insertGameRoom(const std::string & roomUUID, const std::string & hostAccountName); - void insertAccount(const std::string & accountName); - void insertAccessCookie(const std::string & accountName, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime); - void insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomName, const std::string & messageText); + void deleteGameRoomInvite(const std::string & targetAccountID, const std::string & roomID); + void insertGameRoomInvite(const std::string & targetAccountID, const std::string & roomID); - std::vector getActiveGameRooms(); - std::vector getRecentMessageHistory(); + void insertGameRoom(const std::string & roomID, const std::string & hostAccountID); + void insertAccount(const std::string & accountID, const std::string & displayName); + void insertAccessCookie(const std::string & accountID, const std::string & accessCookieUUID); + void insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomID, const std::string & messageText); - bool checkAccessCookie(const std::string & accountName, const std::string & accessCookieUUID); - bool isPlayerInGameRoom(const std::string & accountName); - bool isAccountNameAvailable(const std::string & accountName); + void updateAccessCookie(const std::string & accountID, const std::string & accessCookieUUID); + void updateAccountLoginTime(const std::string & accountID); + void updateActiveAccount(const std::string & accountID, bool isActive); + + std::vector getActiveGameRooms(); + std::vector getActiveAccounts(); + std::vector getAccountsInRoom(const std::string & roomID); + std::vector getRecentMessageHistory(); + + std::string getIdleGameRoom(const std::string & hostAccountID); + std::string getAccountGameRoom(const std::string & accountID); + std::string getAccountDisplayName(const std::string & accountID); + + LobbyCookieStatus getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime); + LobbyCookieStatus getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime); + LobbyCookieStatus getAccountCookieStatus(const std::string & accountID, std::chrono::seconds cookieLifetime); + LobbyInviteStatus getAccountInviteStatus(const std::string & accountID, const std::string & roomID); + LobbyRoomState getGameRoomStatus(const std::string & roomID); + uint32_t getGameRoomFreeSlots(const std::string & roomID); + + bool isPlayerInGameRoom(const std::string & accountID); + bool isPlayerInGameRoom(const std::string & accountID, const std::string & roomID); + bool isAccountExists(const std::string & accountID); }; diff --git a/lobby/LobbyDefines.h b/lobby/LobbyDefines.h new file mode 100644 index 000000000..370e25385 --- /dev/null +++ b/lobby/LobbyDefines.h @@ -0,0 +1,56 @@ +/* + * LobbyDefines.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +struct LobbyAccount +{ + std::string accountID; + std::string displayName; + //std::string status; +}; + +struct LobbyGameRoom +{ + std::string roomUUID; + std::string roomStatus; + uint32_t playersCount; + uint32_t playersLimit; +}; + +struct LobbyChatMessage +{ + std::string sender; + std::string messageText; + std::chrono::seconds age; +}; + +enum class LobbyCookieStatus : int32_t +{ + INVALID, + EXPIRED, + VALID +}; + +enum class LobbyInviteStatus : int32_t +{ + NOT_INVITED, + INVITED, + DECLINED +}; + +enum class LobbyRoomState : int32_t +{ + IDLE, // server is ready but no players are in the room + PUBLIC, // host has joined and allows anybody to join + PRIVATE, // host has joined but only allows those he invited to join + //BUSY, // match is ongoing + //CANCELLED, // game room was cancelled without starting the game + //CLOSED, // game room was closed after playing for some time +}; diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 35c64aa39..cc31d7120 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -14,126 +14,530 @@ #include "../lib/JsonNode.h" #include "../lib/network/NetworkServer.h" +#include "../lib/network/NetworkConnection.h" -void LobbyServer::sendMessage(const std::shared_ptr & target, const JsonNode & json) +#include +#include + +static const auto accountCookieLifetime = std::chrono::hours(24*7); + +bool LobbyServer::isAccountNameValid(const std::string & accountName) { - //FIXME: copy-paste from LobbyClient::sendMessage + if (accountName.size() < 4) + return false; + + if (accountName.size() < 20) + return false; + + for (auto const & c : accountName) + if (!std::isalnum(c)) + return false; + + return true; +} + +std::string LobbyServer::sanitizeChatMessage(const std::string & inputString) const +{ + // TODO: sanitize message and remove any "weird" symbols from it + return inputString; +} + +NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID) +{ + for (auto const & account : activeAccounts) + if (account.second.accountID == accountID) + return account.first; + + return nullptr; +} + +NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) +{ + for (auto const & account : activeGameRooms) + if (account.second.roomID == gameRoomID) + return account.first; + + return nullptr; +} + +void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json) +{ + //NOTE: copy-paste from LobbyClient::sendMessage std::string payloadString = json.toJson(true); - // FIXME: find better approach - uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); - uint8_t * payloadEnd = payloadBegin + payloadString.size(); + // TODO: find better approach + const uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); + const uint8_t * payloadEnd = payloadBegin + payloadString.size(); std::vector payloadBuffer(payloadBegin, payloadEnd); networkServer->sendPacket(target, payloadBuffer); } +void LobbyServer::sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie) +{ + JsonNode reply; + reply["type"].String() = "accountCreated"; + reply["accountID"].String() = accountID; + reply["accountCookie"].String() = accountCookie; + sendMessage(target, reply); +} + +void LobbyServer::sendInviteReceived(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & gameRoomID) +{ + JsonNode reply; + reply["type"].String() = "inviteReceived"; + reply["accountID"].String() = accountID; + reply["gameRoomID"].String() = gameRoomID; + sendMessage(target, reply); +} + +void LobbyServer::sendLoginFailed(const NetworkConnectionPtr & target, const std::string & reason) +{ + JsonNode reply; + reply["type"].String() = "loginFailed"; + reply["reason"].String() = reason; + sendMessage(target, reply); +} + +void LobbyServer::sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie) +{ + JsonNode reply; + reply["type"].String() = "loginSuccess"; + reply["accountCookie"].String() = accountCookie; + sendMessage(target, reply); +} + +void LobbyServer::sendChatHistory(const NetworkConnectionPtr & target, const std::vector & history) +{ + JsonNode reply; + reply["type"].String() = "chatHistory"; + + for(const auto & message : boost::adaptors::reverse(history)) + { + JsonNode jsonEntry; + + jsonEntry["messageText"].String() = message.messageText; + jsonEntry["senderName"].String() = message.sender; + jsonEntry["ageSeconds"].Integer() = message.age.count(); + + reply["messages"].Vector().push_back(jsonEntry); + } + + sendMessage(target, reply); +} + +void LobbyServer::broadcastActiveAccounts() +{ + auto activeAccountsStats = database->getActiveAccounts(); + + JsonNode reply; + reply["type"].String() = "activeAccounts"; + + for(const auto & account : activeAccountsStats) + { + JsonNode jsonEntry; + jsonEntry["accountID"].String() = account.accountID; + jsonEntry["displayName"].String() = account.displayName; +// jsonEntry["status"].String() = account.status; + reply["accounts"].Vector().push_back(jsonEntry); + } + + for(const auto & connection : activeAccounts) + sendMessage(connection.first, reply); +} + +void LobbyServer::broadcastActiveGameRooms() +{ + auto activeGameRoomStats = database->getActiveGameRooms(); + JsonNode reply; + reply["type"].String() = "activeGameRooms"; + + for(const auto & gameRoom : activeGameRoomStats) + { + JsonNode jsonEntry; + jsonEntry["gameRoomID"].String() = gameRoom.roomUUID; + jsonEntry["status"].String() = gameRoom.roomStatus; + jsonEntry["status"].Integer() = gameRoom.playersCount; + jsonEntry["status"].Integer() = gameRoom.playersLimit; + reply["gameRooms"].Vector().push_back(jsonEntry); + } + + for(const auto & connection : activeAccounts) + sendMessage(connection.first, reply); +} + +void LobbyServer::sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID) +{ + JsonNode reply; + reply["type"].String() = "accountJoinsRoom"; + reply["accountID"].String() = accountID; + sendMessage(target, reply); +} + +void LobbyServer::sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID) +{ + JsonNode reply; + reply["type"].String() = "joinRoomSuccess"; + reply["gameRoomID"].String() = gameRoomID; + sendMessage(target, reply); +} + +void LobbyServer::sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & senderName, const std::string & messageText) +{ + JsonNode reply; + reply["type"].String() = "chatMessage"; + reply["messageText"].String() = messageText; + reply["senderName"].String() = senderName; + reply["roomMode"].String() = roomMode; + reply["roomName"].String() = roomName; + + sendMessage(target, reply); +} + void LobbyServer::onTimer() { // no-op } -void LobbyServer::onNewConnection(const std::shared_ptr & connection) -{} - -void LobbyServer::onDisconnected(const std::shared_ptr & connection) +void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection) { - activeAccounts.erase(connection); + // no-op - waiting for incoming data } -void LobbyServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) +void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection) { - // FIXME: find better approach + // NOTE: lost connection can be in only one of these lists (or in none of them) + // calling on all possible containers since calling std::map::erase() with non-existing key is legal + activeAccounts.erase(connection); + activeProxies.erase(connection); + activeGameRooms.erase(connection); + + broadcastActiveAccounts(); + broadcastActiveGameRooms(); +} + +void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, const std::vector & message) +{ + // proxy connection - no processing, only redirect + if (activeProxies.count(connection)) + { + auto lockedPtr = activeProxies.at(connection).lock(); + if (lockedPtr) + lockedPtr->sendPacket(message); + return; + } + JsonNode json(message.data(), message.size()); - if(json["type"].String() == "sendChatMessage") - return receiveSendChatMessage(connection, json); + // TODO: check for json parsing errors + // TODO: validate json based on received message type - if(json["type"].String() == "authentication") - return receiveAuthentication(connection, json); + // communication messages from vcmiclient + if (activeAccounts.count(connection)) + { + if(json["type"].String() == "sendChatMessage") + return receiveSendChatMessage(connection, json); - if(json["type"].String() == "joinGameRoom") - return receiveJoinGameRoom(connection, json); + if(json["type"].String() == "openGameRoom") + return receiveOpenGameRoom(connection, json); + + if(json["type"].String() == "joinGameRoom") + return receiveJoinGameRoom(connection, json); + + if(json["type"].String() == "sendInvite") + return receiveSendInvite(connection, json); + + if(json["type"].String() == "declineInvite") + return receiveDeclineInvite(connection, json); + + return; + } + + // communication messages from vcmiserver + if (activeGameRooms.count(connection)) + { + if(json["type"].String() == "leaveGameRoom") + return receiveLeaveGameRoom(connection, json); + + return; + } + + // unauthorized connections - permit only login or register attempts + if(json["type"].String() == "clientLogin") + return receiveClientLogin(connection, json); + + if(json["type"].String() == "clientRegister") + return receiveClientRegister(connection, json); + + if(json["type"].String() == "serverLogin") + return receiveServerLogin(connection, json); + + if(json["type"].String() == "clientProxyLogin") + return receiveClientProxyLogin(connection, json); + + if(json["type"].String() == "serverProxyLogin") + return receiveServerProxyLogin(connection, json); + + // TODO: add logging of suspicious connections. + networkServer->closeConnection(connection); } -void LobbyServer::receiveSendChatMessage(const std::shared_ptr & connection, const JsonNode & json) +void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json) { - if(activeAccounts.count(connection) == 0) - return; // unauthenticated - - std::string senderName = activeAccounts[connection].accountName; + std::string senderName = activeAccounts[connection].accountID; std::string messageText = json["messageText"].String(); + std::string messageTextClean = sanitizeChatMessage(messageText); - database->insertChatMessage(senderName, "general", "english", messageText); + if (messageTextClean.empty()) + return; - JsonNode reply; - reply["type"].String() = "chatMessage"; - reply["messageText"].String() = messageText; - reply["senderName"].String() = senderName; + database->insertChatMessage(senderName, "global", "english", messageText); for(const auto & connection : activeAccounts) - sendMessage(connection.first, reply); + sendChatMessage(connection.first, "global", "english", senderName, messageText); } -void LobbyServer::receiveAuthentication(const std::shared_ptr & connection, const JsonNode & json) +void LobbyServer::receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json) { - std::string accountName = json["accountName"].String(); + std::string accountID = json["accountID"].String(); + std::string displayName = json["displayName"].String(); + std::string language = json["language"].String(); - // TODO: account cookie check - // TODO: account password check - // TODO: protocol version number - // TODO: client/server mode flag - // TODO: client language + if (database->isAccountExists(accountID)) + return sendLoginFailed(connection, "Account name already in use"); - activeAccounts[connection].accountName = accountName; + if (isAccountNameValid(accountID)) + return sendLoginFailed(connection, "Illegal account name"); + std::string accountCookie = boost::uuids::to_string(boost::uuids::random_generator()()); + + database->insertAccount(accountID, displayName); + database->insertAccessCookie(accountID, accountCookie); + + sendAccountCreated(connection, accountID, accountCookie); +} + +void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, const JsonNode & json) +{ + std::string accountID = json["accountID"].String(); + std::string accountCookie = json["accountCookie"].String(); + std::string language = json["language"].String(); + std::string version = json["version"].String(); + + if (!database->isAccountExists(accountID)) + return sendLoginFailed(connection, "Account not found"); + + auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime); + + if (clientCookieStatus == LobbyCookieStatus::INVALID) + return sendLoginFailed(connection, "Authentification failure"); + + // prolong existing cookie + database->updateAccessCookie(accountID, accountCookie); + database->updateAccountLoginTime(accountID); + + std::string displayName = database->getAccountDisplayName(accountID); + + activeAccounts[connection].accountID = accountID; + activeAccounts[connection].displayName = displayName; + activeAccounts[connection].version = version; + activeAccounts[connection].language = language; + + sendLoginSuccess(connection, accountCookie); + sendChatHistory(connection, database->getRecentMessageHistory()); + + // send active accounts list to new account + // and update acount list to everybody else + broadcastActiveAccounts(); +} + +void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, const JsonNode & json) +{ + std::string gameRoomID = json["gameRoomID"].String(); + std::string accountID = json["accountID"].String(); + std::string accountCookie = json["accountCookie"].String(); + std::string version = json["version"].String(); + + auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime); + + if (clientCookieStatus == LobbyCookieStatus::INVALID) { - JsonNode reply; - reply["type"].String() = "authentication"; - - sendMessage(connection, reply); + sendLoginFailed(connection, "Invalid credentials"); } - - auto history = database->getRecentMessageHistory(); - + else { - JsonNode reply; - reply["type"].String() = "chatHistory"; + database->insertGameRoom(gameRoomID, accountID); + activeGameRooms[connection].roomID = gameRoomID; + sendLoginSuccess(connection, accountCookie); + } +} - for(const auto & message : boost::adaptors::reverse(history)) +void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json) +{ + std::string gameRoomID = json["gameRoomID"].String(); + std::string accountID = json["accountID"].String(); + std::string accountCookie = json["accountCookie"].String(); + + auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime); + + if (clientCookieStatus != LobbyCookieStatus::INVALID) + { + for (auto & proxyEntry : awaitingProxies) { - JsonNode jsonEntry; + if (proxyEntry.accountID != accountID) + continue; + if (proxyEntry.roomID != gameRoomID) + continue; - jsonEntry["messageText"].String() = message.messageText; - jsonEntry["senderName"].String() = message.sender; - jsonEntry["ageSeconds"].Integer() = message.age.count(); + proxyEntry.accountConnection = connection; - reply["messages"].Vector().push_back(jsonEntry); + auto gameRoomConnection = proxyEntry.roomConnection.lock(); + + if (gameRoomConnection) + { + activeProxies[gameRoomConnection] = connection; + activeProxies[connection] = gameRoomConnection; + } + return; } - - sendMessage(connection, reply); } + + networkServer->closeConnection(connection); } -void LobbyServer::receiveJoinGameRoom(const std::shared_ptr & connection, const JsonNode & json) +void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json) { - if(activeAccounts.count(connection) == 0) - return; // unauthenticated + std::string gameRoomID = json["gameRoomID"].String(); + std::string guestAccountID = json["guestAccountID"].String(); + std::string hostCookie = json["hostCookie"].String(); - std::string senderName = activeAccounts[connection].accountName; + auto clientCookieStatus = database->getGameRoomCookieStatus(gameRoomID, hostCookie, accountCookieLifetime); - if(database->isPlayerInGameRoom(senderName)) + if (clientCookieStatus != LobbyCookieStatus::INVALID) + { + NetworkConnectionPtr targetAccount = findAccount(guestAccountID); + + if (targetAccount == nullptr) + return; // unknown / disconnected account + + sendJoinRoomSuccess(targetAccount, gameRoomID); + + AwaitingProxyState proxy; + proxy.accountID = guestAccountID; + proxy.roomID = gameRoomID; + proxy.roomConnection = connection; + awaitingProxies.push_back(proxy); + return; + } + + networkServer->closeConnection(connection); +} + +void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json) +{ + std::string hostAccountID = json["hostAccountID"].String(); + std::string accountID = activeAccounts[connection].accountID; + + if(database->isPlayerInGameRoom(accountID)) return; // only 1 room per player allowed - // TODO: roomType: private, public - // TODO: additional flags, e.g. allowCheats - // TODO: connection mode: direct or proxy + std::string gameRoomID = database->getIdleGameRoom(hostAccountID); + if (gameRoomID.empty()) + return; + + std::string roomType = json["roomType"].String(); + if (roomType == "public") + database->setGameRoomStatus(gameRoomID, LobbyRoomState::PUBLIC); + if (roomType == "private") + database->setGameRoomStatus(gameRoomID, LobbyRoomState::PRIVATE); + + // TODO: additional flags / initial settings, e.g. allowCheats + // TODO: connection mode: direct or proxy. For now direct is assumed + + broadcastActiveGameRooms(); + sendJoinRoomSuccess(connection, gameRoomID); +} + +void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json) +{ + std::string gameRoomID = json["gameRoomID"].String(); + std::string accountID = activeAccounts[connection].accountID; + + if(database->isPlayerInGameRoom(accountID)) + return; // only 1 room per player allowed + + NetworkConnectionPtr targetRoom = findGameRoom(gameRoomID); + + if (targetRoom == nullptr) + return; // unknown / disconnected room + + auto roomStatus = database->getGameRoomStatus(gameRoomID); + + if (roomStatus == LobbyRoomState::PRIVATE) + { + if (database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED) + return; + } + + if (database->getGameRoomFreeSlots(gameRoomID) == 0) + return; + + sendAccountJoinsRoom(targetRoom, accountID); + //No reply to client - will be sent once match server establishes proxy connection with lobby + + broadcastActiveGameRooms(); +} + +void LobbyServer::receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json) +{ + std::string gameRoomID = json["gameRoomID"].String(); + std::string senderName = activeAccounts[connection].accountID; + + if(!database->isPlayerInGameRoom(senderName, gameRoomID)) + return; + + broadcastActiveGameRooms(); +} + +void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, const JsonNode & json) +{ + std::string senderName = activeAccounts[connection].accountID; + std::string accountID = json["accountID"].String(); + std::string gameRoomID = database->getAccountGameRoom(senderName); + + auto targetAccount = findAccount(accountID); + + if (!targetAccount) + return; // target player does not exists or offline + + if(!database->isPlayerInGameRoom(senderName)) + return; // current player is not in room + + if(database->isPlayerInGameRoom(accountID)) + return; // target player is busy + + if (database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::NOT_INVITED) + return; // already has invite + + database->insertGameRoomInvite(accountID, gameRoomID); + sendInviteReceived(targetAccount, senderName, gameRoomID); +} + +void LobbyServer::receiveDeclineInvite(const NetworkConnectionPtr & connection, const JsonNode & json) +{ + std::string accountID = activeAccounts[connection].accountID; + std::string gameRoomID = json["gameRoomID"].String(); + + if (database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED) + return; // already has invite + + database->deleteGameRoomInvite(accountID, gameRoomID); } LobbyServer::~LobbyServer() = default; -LobbyServer::LobbyServer(const std::string & databasePath) +LobbyServer::LobbyServer(const boost::filesystem::path & databasePath) : database(new LobbyDatabase(databasePath)) , networkServer(new NetworkServer(*this)) { diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index c9f278349..c76b4f9ec 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -9,6 +9,7 @@ */ #pragma once +#include "LobbyDefines.h" #include "../lib/network/NetworkListener.h" VCMI_LIB_NAMESPACE_BEGIN @@ -21,29 +22,78 @@ class LobbyServer : public INetworkServerListener { struct AccountState { - std::string accountName; + std::string accountID; + std::string displayName; + std::string version; + std::string language; + }; + struct GameRoomState + { + std::string roomID; + }; + struct AwaitingProxyState + { + std::string accountID; + std::string roomID; + std::weak_ptr accountConnection; + std::weak_ptr roomConnection; }; - std::map, AccountState> activeAccounts; + /// list of connected proxies. All messages received from (key) will be redirected to (value) connection + std::map> activeProxies; + + /// list of half-established proxies from server that are still waiting for client to connect + std::vector awaitingProxies; + + /// list of logged in accounts (vcmiclient's) + std::map activeAccounts; + + /// list of currently logged in game rooms (vcmiserver's) + std::map activeGameRooms; std::unique_ptr database; std::unique_ptr networkServer; + std::string sanitizeChatMessage(const std::string & inputString) const; bool isAccountNameValid(const std::string & accountName); - void onNewConnection(const std::shared_ptr &) override; - void onDisconnected(const std::shared_ptr &) override; - void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; + NetworkConnectionPtr findAccount(const std::string & accountID); + NetworkConnectionPtr findGameRoom(const std::string & gameRoomID); + + void onNewConnection(const NetworkConnectionPtr & connection) override; + void onDisconnected(const NetworkConnectionPtr & connection) override; + void onPacketReceived(const NetworkConnectionPtr & connection, const std::vector & message) override; void onTimer() override; - void sendMessage(const std::shared_ptr & target, const JsonNode & json); + void sendMessage(const NetworkConnectionPtr & target, const JsonNode & json); - void receiveSendChatMessage(const std::shared_ptr & connection, const JsonNode & json); - void receiveAuthentication(const std::shared_ptr & connection, const JsonNode & json); - void receiveJoinGameRoom(const std::shared_ptr & connection, const JsonNode & json); + void broadcastActiveAccounts(); + void broadcastActiveGameRooms(); + + void sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & senderName, const std::string & messageText); + void sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie); + void sendLoginFailed(const NetworkConnectionPtr & target, const std::string & reason); + void sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie); + void sendChatHistory(const NetworkConnectionPtr & target, const std::vector &); + void sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID); + void sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID); + void sendInviteReceived(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & gameRoomID); + + void receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json); + void receiveClientLogin(const NetworkConnectionPtr & connection, const JsonNode & json); + void receiveServerLogin(const NetworkConnectionPtr & connection, const JsonNode & json); + void receiveClientProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json); + void receiveServerProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json); + + void receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json); + void receiveOpenGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json); + void receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json); + void receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json); + void receiveSendInvite(const NetworkConnectionPtr & connection, const JsonNode & json); + void receiveDeclineInvite(const NetworkConnectionPtr & connection, const JsonNode & json); public: - LobbyServer(const std::string & databasePath); + explicit LobbyServer(const boost::filesystem::path & databasePath); ~LobbyServer(); void start(uint16_t port); diff --git a/lobby/SQLiteConnection.cpp b/lobby/SQLiteConnection.cpp index 40fd62d86..3fd70a363 100644 --- a/lobby/SQLiteConnection.cpp +++ b/lobby/SQLiteConnection.cpp @@ -70,6 +70,12 @@ void SQLiteStatement::setBindSingle(size_t index, const double & value) checkSQLiteError(m_instance.m_connection, result); } +void SQLiteStatement::setBindSingle(size_t index, const bool & value) +{ + int result = sqlite3_bind_int(m_statement, static_cast(value), value); + checkSQLiteError(m_instance.m_connection, result); +} + void SQLiteStatement::setBindSingle(size_t index, const uint8_t & value) { int result = sqlite3_bind_int(m_statement, static_cast(index), value); @@ -116,6 +122,11 @@ void SQLiteStatement::getColumnSingle(size_t index, double & value) value = sqlite3_column_double(m_statement, static_cast(index)); } +void SQLiteStatement::getColumnSingle(size_t index, bool & value) +{ + value = sqlite3_column_int(m_statement, static_cast(index)) != 0; +} + void SQLiteStatement::getColumnSingle(size_t index, uint8_t & value) { value = static_cast(sqlite3_column_int(m_statement, static_cast(index))); @@ -147,7 +158,7 @@ void SQLiteStatement::getColumnSingle(size_t index, std::string & value) value = reinterpret_cast(value_raw); } -SQLiteInstancePtr SQLiteInstance::open(const std::string & db_path, bool allow_write) +SQLiteInstancePtr SQLiteInstance::open(const boost::filesystem::path & db_path, bool allow_write) { int flags = allow_write ? (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) : SQLITE_OPEN_READONLY; diff --git a/lobby/SQLiteConnection.h b/lobby/SQLiteConnection.h index d80c426b7..268b1705e 100644 --- a/lobby/SQLiteConnection.h +++ b/lobby/SQLiteConnection.h @@ -29,6 +29,14 @@ public: ~SQLiteStatement(); + template + void executeOnce(const Args &... args) + { + setBinds(args...); + execute(); + reset(); + } + template void setBinds(const Args &... args) { @@ -43,6 +51,7 @@ public: private: void setBindSingle(size_t index, const double & value); + void setBindSingle(size_t index, const bool & value); void setBindSingle(size_t index, const uint8_t & value); void setBindSingle(size_t index, const uint16_t & value); void setBindSingle(size_t index, const uint32_t & value); @@ -52,6 +61,7 @@ private: void setBindSingle(size_t index, const char * value); void getColumnSingle(size_t index, double & value); + void getColumnSingle(size_t index, bool & value); void getColumnSingle(size_t index, uint8_t & value); void getColumnSingle(size_t index, uint16_t & value); void getColumnSingle(size_t index, uint32_t & value); @@ -92,7 +102,7 @@ class SQLiteInstance : boost::noncopyable public: friend class SQLiteStatement; - static SQLiteInstancePtr open(const std::string & db_path, bool allow_write); + static SQLiteInstancePtr open(const boost::filesystem::path & db_path, bool allow_write); ~SQLiteInstance(); From 47f72af55699f45eedc9bfb997ebd9bf05c50a52 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 8 Jan 2024 20:50:43 +0200 Subject: [PATCH 28/98] Client-side update, adapted and fixed login and chat --- client/globalLobby/GlobalLobbyClient.cpp | 157 ++++++++++++++---- client/globalLobby/GlobalLobbyClient.h | 9 + client/globalLobby/GlobalLobbyLoginWindow.cpp | 13 +- client/globalLobby/GlobalLobbyWindow.cpp | 2 +- config/schemas/settings.json | 28 +++- lib/TextOperations.cpp | 6 + lib/TextOperations.h | 3 + lobby/LobbyDatabase.cpp | 79 ++++++--- lobby/LobbyDatabase.h | 8 +- lobby/LobbyDefines.h | 3 +- lobby/LobbyServer.cpp | 35 ++-- lobby/LobbyServer.h | 4 +- 12 files changed, 267 insertions(+), 80 deletions(-) diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index 043efd9c8..bcef12a03 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -20,6 +20,7 @@ #include "../../lib/MetaString.h" #include "../../lib/CConfigHandler.h" +#include "../../lib/TextOperations.h" #include "../../lib/network/NetworkClient.h" GlobalLobbyClient::~GlobalLobbyClient() @@ -42,15 +43,7 @@ static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0) auto timeNowChrono = std::chrono::system_clock::now(); timeNowChrono += std::chrono::seconds(timeOffsetSeconds); - std::time_t timeNowC = std::chrono::system_clock::to_time_t(timeNowChrono); - std::tm timeNowTm = *std::localtime(&timeNowC); - - MetaString timeFormatted; - timeFormatted.appendRawString("%d:%d"); - timeFormatted.replaceNumber(timeNowTm.tm_hour); - timeFormatted.replaceNumber(timeNowTm.tm_min); - - return timeFormatted.toString(); + return TextOperations::getFormattedTimeLocal(std::chrono::system_clock::to_time_t(timeNowChrono)); } void GlobalLobbyClient::onPacketReceived(const std::shared_ptr &, const std::vector & message) @@ -59,48 +52,138 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptr()) - throw std::runtime_error("lobby connection finished without active login window!"); + if (json["type"].String() == "loginFailed") + return receiveLoginFailed(json); - loginWindowPtr->onConnectionSuccess(); - } + if (json["type"].String() == "loginSuccess") + return receiveLoginSuccess(json); if (json["type"].String() == "chatHistory") - { - for (auto const & entry : json["messages"].Vector()) - { - std::string senderName = entry["senderName"].String(); - std::string messageText = entry["messageText"].String(); - int ageSeconds = entry["ageSeconds"].Integer(); - std::string timeFormatted = getCurrentTimeFormatted(-ageSeconds); - - auto lobbyWindowPtr = lobbyWindow.lock(); - if(lobbyWindowPtr) - lobbyWindowPtr->onGameChatMessage(senderName, messageText, timeFormatted); - } - } + return receiveChatHistory(json); if (json["type"].String() == "chatMessage") + return receiveChatMessage(json); + + if (json["type"].String() == "activeAccounts") + return receiveActiveAccounts(json); + + throw std::runtime_error("Received unexpected message from lobby server: " + json["type"].String() ); +} + +void GlobalLobbyClient::receiveAccountCreated(const JsonNode & json) +{ + auto loginWindowPtr = loginWindow.lock(); + + if (!loginWindowPtr || !GH.windows().topWindow()) + throw std::runtime_error("lobby connection finished without active login window!"); + { - std::string senderName = json["senderName"].String(); - std::string messageText = json["messageText"].String(); - std::string timeFormatted = getCurrentTimeFormatted(); + Settings configID = settings.write["lobby"]["accountID"]; + configID->String() = json["accountID"].String(); + + Settings configName = settings.write["lobby"]["displayName"]; + configName->String() = json["displayName"].String(); + + Settings configCookie = settings.write["lobby"]["accountCookie"]; + configCookie->String() = json["accountCookie"].String(); + } + + sendClientLogin(); +} + +void GlobalLobbyClient::receiveLoginFailed(const JsonNode & json) +{ + auto loginWindowPtr = loginWindow.lock(); + + if (!loginWindowPtr || !GH.windows().topWindow()) + throw std::runtime_error("lobby connection finished without active login window!"); + + loginWindowPtr->onConnectionFailed(json["reason"].String()); +} + +void GlobalLobbyClient::receiveLoginSuccess(const JsonNode & json) +{ + { + Settings configCookie = settings.write["lobby"]["accountCookie"]; + configCookie->String() = json["accountCookie"].String(); + + Settings configName = settings.write["lobby"]["displayName"]; + configName->String() = json["displayName"].String(); + } + + auto loginWindowPtr = loginWindow.lock(); + + if (!loginWindowPtr || !GH.windows().topWindow()) + throw std::runtime_error("lobby connection finished without active login window!"); + + loginWindowPtr->onConnectionSuccess(); +} + +void GlobalLobbyClient::receiveChatHistory(const JsonNode & json) +{ + for (auto const & entry : json["messages"].Vector()) + { + std::string accountID = entry["accountID"].String(); + std::string displayName = entry["displayName"].String(); + std::string messageText = entry["messageText"].String(); + int ageSeconds = entry["ageSeconds"].Integer(); + std::string timeFormatted = getCurrentTimeFormatted(-ageSeconds); + auto lobbyWindowPtr = lobbyWindow.lock(); if(lobbyWindowPtr) - lobbyWindowPtr->onGameChatMessage(senderName, messageText, timeFormatted); + lobbyWindowPtr->onGameChatMessage(displayName, messageText, timeFormatted); } } +void GlobalLobbyClient::receiveChatMessage(const JsonNode & json) +{ + std::string accountID = json["accountID"].String(); + std::string displayName = json["displayName"].String(); + std::string messageText = json["messageText"].String(); + std::string timeFormatted = getCurrentTimeFormatted(); + auto lobbyWindowPtr = lobbyWindow.lock(); + if(lobbyWindowPtr) + lobbyWindowPtr->onGameChatMessage(displayName, messageText, timeFormatted); +} + +void GlobalLobbyClient::receiveActiveAccounts(const JsonNode & json) +{ + //for (auto const & jsonEntry : json["messages"].Vector()) + //{ + // std::string accountID = jsonEntry["accountID"].String(); + // std::string displayName = jsonEntry["displayName"].String(); + //} +} + void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr &) { JsonNode toSend; - toSend["type"].String() = "authentication"; - toSend["accountName"].String() = settings["general"]["playerName"].String(); + std::string accountID = settings["lobby"]["accountID"].String(); + + if (accountID.empty()) + sendClientRegister(); + else + sendClientLogin(); +} + +void GlobalLobbyClient::sendClientRegister() +{ + JsonNode toSend; + toSend["type"].String() = "clientRegister"; + toSend["displayName"] = settings["lobby"]["displayName"]; + sendMessage(toSend); +} + +void GlobalLobbyClient::sendClientLogin() +{ + JsonNode toSend; + toSend["type"].String() = "clientLogin"; + toSend["accountID"] = settings["lobby"]["accountID"]; + toSend["accountCookie"] = settings["lobby"]["accountCookie"]; sendMessage(toSend); } @@ -141,7 +224,9 @@ void GlobalLobbyClient::sendMessage(const JsonNode & data) void GlobalLobbyClient::connect() { - networkClient->start("127.0.0.1", 30303); + std::string hostname = settings["lobby"]["hostname"].String(); + int16_t port = settings["lobby"]["port"].Integer(); + networkClient->start(hostname, port); } bool GlobalLobbyClient::isConnected() diff --git a/client/globalLobby/GlobalLobbyClient.h b/client/globalLobby/GlobalLobbyClient.h index 50e1020d8..461cf7e81 100644 --- a/client/globalLobby/GlobalLobbyClient.h +++ b/client/globalLobby/GlobalLobbyClient.h @@ -33,6 +33,15 @@ class GlobalLobbyClient : public INetworkClientListener, boost::noncopyable void onDisconnected(const std::shared_ptr &) override; void onTimer() override; + void sendClientRegister(); + void sendClientLogin(); + + void receiveAccountCreated(const JsonNode & json); + void receiveLoginFailed(const JsonNode & json); + void receiveLoginSuccess(const JsonNode & json); + void receiveChatHistory(const JsonNode & json); + void receiveChatMessage(const JsonNode & json); + void receiveActiveAccounts(const JsonNode & json); public: explicit GlobalLobbyClient(); ~GlobalLobbyClient(); diff --git a/client/globalLobby/GlobalLobbyLoginWindow.cpp b/client/globalLobby/GlobalLobbyLoginWindow.cpp index 6e4056d68..783cdb9ba 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.cpp +++ b/client/globalLobby/GlobalLobbyLoginWindow.cpp @@ -25,6 +25,7 @@ #include "../../lib/CGeneralTextHandler.h" #include "../../lib/MetaString.h" +#include "../../lib/CConfigHandler.h" GlobalLobbyLoginWindow::GlobalLobbyLoginWindow() : CWindowObject(BORDERED) @@ -41,9 +42,14 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow() inputUsername = std::make_shared(Rect(15, 73, 176, 16), FONT_SMALL, nullptr, ETextAlignment::TOPLEFT, true); buttonLogin = std::make_shared(Point(10, 160), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onLogin(); }); buttonClose = std::make_shared(Point(126, 160), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }); - labelStatus = std::make_shared( "", Rect(15, 95, 175, 60), 1, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE); + labelStatus = std::make_shared( "", Rect(15, 95, 175, 60), 1, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); background->playerColored(PlayerColor(1)); + inputUsername->setText(settings["lobby"]["displayName"].String()); + inputUsername->cb += [this](const std::string & text) + { + buttonLogin->block(text.empty()); + }; center(); } @@ -56,8 +62,12 @@ void GlobalLobbyLoginWindow::onClose() void GlobalLobbyLoginWindow::onLogin() { + Settings config = settings.write["lobby"]["displayName"]; + config->String() = inputUsername->getText(); + labelStatus->setText(CGI->generaltexth->translate("vcmi.lobby.login.connecting")); CSH->getGlobalLobby().connect(); + buttonClose->block(true); } void GlobalLobbyLoginWindow::onConnectionSuccess() @@ -73,4 +83,5 @@ void GlobalLobbyLoginWindow::onConnectionFailed(const std::string & reason) formatter.replaceRawString(reason); labelStatus->setText(formatter.toString()); + buttonClose->block(false); } diff --git a/client/globalLobby/GlobalLobbyWindow.cpp b/client/globalLobby/GlobalLobbyWindow.cpp index fa8b7bd1d..c4b5f7fb0 100644 --- a/client/globalLobby/GlobalLobbyWindow.cpp +++ b/client/globalLobby/GlobalLobbyWindow.cpp @@ -29,7 +29,7 @@ GlobalLobbyWindow::GlobalLobbyWindow(): pos = widget->pos; center(); - widget->getAccountNameLabel()->setText(settings["general"]["playerName"].String()); + widget->getAccountNameLabel()->setText(settings["lobby"]["displayName"].String()); } void GlobalLobbyWindow::doSendChatMessage() diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 8f5595c42..73d7b6df6 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -553,12 +553,36 @@ "type" : "object", "additionalProperties" : false, "default" : {}, - "required" : [ "mapPreview" ], + "required" : [ "mapPreview", "accountID", "accountCookie", "displayName", "hostname", "port" ], "properties" : { "mapPreview" : { "type" : "boolean", "default" : true - } + }, + + "accountID" : { + "type" : "string", + "default" : "" + }, + + "accountCookie" : { + "type" : "string", + "default" : "" + }, + + "displayName" : { + "type" : "string", + "default" : "" + }, + + "hostname" : { + "type" : "string", + "default" : "127.0.0.1" + }, + "port" : { + "type" : "number", + "default" : 30303 + }, } }, "gameTweaks" : { diff --git a/lib/TextOperations.cpp b/lib/TextOperations.cpp index 87dc765e2..d234a848c 100644 --- a/lib/TextOperations.cpp +++ b/lib/TextOperations.cpp @@ -219,4 +219,10 @@ std::string TextOperations::getFormattedDateTimeLocal(std::time_t dt) return vstd::getFormattedDateTime(dt, Languages::getLanguageOptions(settings["general"]["language"].String()).dateTimeFormat); } +std::string TextOperations::getFormattedTimeLocal(std::time_t dt) +{ + return vstd::getFormattedDateTime(dt, "%H:%M"); +} + + VCMI_LIB_NAMESPACE_END diff --git a/lib/TextOperations.h b/lib/TextOperations.h index ba944d641..8c0f827f6 100644 --- a/lib/TextOperations.h +++ b/lib/TextOperations.h @@ -59,6 +59,9 @@ namespace TextOperations /// get formatted DateTime depending on the language selected DLL_LINKAGE std::string getFormattedDateTimeLocal(std::time_t dt); + + /// get formatted time (without date) + DLL_LINKAGE std::string getFormattedTimeLocal(std::time_t dt); }; diff --git a/lobby/LobbyDatabase.cpp b/lobby/LobbyDatabase.cpp index e6c5b6158..30c0a2e7a 100644 --- a/lobby/LobbyDatabase.cpp +++ b/lobby/LobbyDatabase.cpp @@ -49,7 +49,7 @@ void LobbyDatabase::createTables() accountID TEXT, displayName TEXT, online INTEGER NOT NULL, - lastLoginTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL + lastLoginTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL ); )"; @@ -110,11 +110,11 @@ void LobbyDatabase::prepareStatements() // DELETE FROM static const std::string deleteGameRoomPlayersText = R"( - DELETE FROM gameRoomPlayers WHERE gameRoomID = ? AND accountID = ? + DELETE FROM gameRoomPlayers WHERE roomID = ? AND accountID = ? )"; static const std::string deleteGameRoomInvitesText = R"( - DELETE FROM gameRoomInvites WHERE gameRoomID = ? AND accountID = ? + DELETE FROM gameRoomInvites WHERE roomID = ? AND accountID = ? )"; // UPDATE @@ -134,10 +134,11 @@ void LobbyDatabase::prepareStatements() // SELECT FROM static const std::string getRecentMessageHistoryText = R"( - SELECT senderName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',creationTime) AS secondsElapsed - FROM chatMessages + SELECT senderName, displayName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',cm.creationTime) AS secondsElapsed + FROM chatMessages cm + LEFT JOIN accounts on accountID = senderName WHERE secondsElapsed < 60*60*18 - ORDER BY creationTime DESC + ORDER BY cm.creationTime DESC LIMIT 100 )"; @@ -149,7 +150,7 @@ void LobbyDatabase::prepareStatements() )"; static const std::string getAccountGameRoomText = R"( - SELECT roomID + SELECT grp.roomID FROM gameRoomPlayers grp LEFT JOIN gameRooms gr ON gr.roomID = grp.roomID WHERE accountID = ? AND status IN ('public', 'private', 'busy') @@ -159,7 +160,13 @@ void LobbyDatabase::prepareStatements() static const std::string getActiveAccountsText = R"( SELECT accountID, displayName FROM accounts - WHERE online <> 1 + WHERE online = 1 + )"; + + static const std::string getAccountDisplayNameText = R"( + SELECT displayName + FROM accounts + WHERE accountID = ? )"; static const std::string isAccountCookieValidText = R"( @@ -187,12 +194,18 @@ void LobbyDatabase::prepareStatements() WHERE accountID = ? )"; - static const std::string isAccountExistsText = R"( + static const std::string isAccountIDExistsText = R"( SELECT COUNT(accountID) FROM accounts WHERE accountID = ? )"; + static const std::string isAccountNameExistsText = R"( + SELECT COUNT(displayName) + FROM accounts + WHERE displayName = ? + )"; + insertChatMessageStatement = database->prepare(insertChatMessageText); insertAccountStatement = database->prepare(insertAccountText); insertAccessCookieStatement = database->prepare(insertAccessCookieText); @@ -210,11 +223,13 @@ void LobbyDatabase::prepareStatements() getIdleGameRoomStatement = database->prepare(getIdleGameRoomText); getAccountGameRoomStatement = database->prepare(getAccountGameRoomText); getActiveAccountsStatement = database->prepare(getActiveAccountsText); + getAccountDisplayNameStatement = database->prepare(getAccountDisplayNameText); isAccountCookieValidStatement = database->prepare(isAccountCookieValidText); isPlayerInGameRoomStatement = database->prepare(isPlayerInGameRoomText); isPlayerInAnyGameRoomStatement = database->prepare(isPlayerInAnyGameRoomText); - isAccountExistsStatement = database->prepare(isAccountExistsText); + isAccountIDExistsStatement = database->prepare(isAccountIDExistsText); + isAccountNameExistsStatement = database->prepare(isAccountNameExistsText); } LobbyDatabase::~LobbyDatabase() = default; @@ -262,7 +277,7 @@ std::vector LobbyDatabase::getRecentMessageHistory() while(getRecentMessageHistoryStatement->execute()) { LobbyChatMessage message; - getRecentMessageHistoryStatement->getColumns(message.sender, message.messageText, message.age); + getRecentMessageHistoryStatement->getColumns(message.accountID, message.displayName, message.messageText, message.age); result.push_back(message); } getRecentMessageHistoryStatement->reset(); @@ -332,7 +347,14 @@ void LobbyDatabase::updateActiveAccount(const std::string & accountID, bool isAc std::string LobbyDatabase::getAccountDisplayName(const std::string & accountID) { - return {}; + std::string result; + + getAccountDisplayNameStatement->setBinds(accountID); + if (getAccountDisplayNameStatement->execute()) + getAccountDisplayNameStatement->getColumns(result); + getAccountDisplayNameStatement->reset(); + + return result; } LobbyCookieStatus LobbyDatabase::getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime) @@ -342,12 +364,14 @@ LobbyCookieStatus LobbyDatabase::getGameRoomCookieStatus(const std::string & acc LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime) { - return {}; -} + bool result = false; -LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & accountID, std::chrono::seconds cookieLifetime) -{ - return {}; + isAccountCookieValidStatement->setBinds(accountID, accessCookieUUID, cookieLifetime.count()); + if (isAccountCookieValidStatement->execute()) + isAccountCookieValidStatement->getColumns(result); + isAccountCookieValidStatement->reset(); + + return result ? LobbyCookieStatus::VALID : LobbyCookieStatus::INVALID; } LobbyInviteStatus LobbyDatabase::getAccountInviteStatus(const std::string & accountID, const std::string & roomID) @@ -365,9 +389,26 @@ uint32_t LobbyDatabase::getGameRoomFreeSlots(const std::string & roomID) return 0; } -bool LobbyDatabase::isAccountExists(const std::string & accountID) +bool LobbyDatabase::isAccountNameExists(const std::string & displayName) { - return false; + bool result = false; + + isAccountNameExistsStatement->setBinds(displayName); + if (isAccountNameExistsStatement->execute()) + isAccountNameExistsStatement->getColumns(result); + isAccountNameExistsStatement->reset(); + return result; +} + +bool LobbyDatabase::isAccountIDExists(const std::string & accountID) +{ + bool result = false; + + isAccountIDExistsStatement->setBinds(accountID); + if (isAccountIDExistsStatement->execute()) + isAccountIDExistsStatement->getColumns(result); + isAccountIDExistsStatement->reset(); + return result; } std::vector LobbyDatabase::getActiveGameRooms() diff --git a/lobby/LobbyDatabase.h b/lobby/LobbyDatabase.h index 4bc172ea0..9768ff886 100644 --- a/lobby/LobbyDatabase.h +++ b/lobby/LobbyDatabase.h @@ -38,12 +38,14 @@ class LobbyDatabase SQLiteStatementPtr getIdleGameRoomStatement; SQLiteStatementPtr getAccountGameRoomStatement; SQLiteStatementPtr getActiveAccountsStatement; + SQLiteStatementPtr getAccountDisplayNameStatement; SQLiteStatementPtr isAccountCookieValidStatement; SQLiteStatementPtr isGameRoomCookieValidStatement; SQLiteStatementPtr isPlayerInGameRoomStatement; SQLiteStatementPtr isPlayerInAnyGameRoomStatement; - SQLiteStatementPtr isAccountExistsStatement; + SQLiteStatementPtr isAccountIDExistsStatement; + SQLiteStatementPtr isAccountNameExistsStatement; void prepareStatements(); void createTables(); @@ -81,12 +83,12 @@ public: LobbyCookieStatus getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime); LobbyCookieStatus getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime); - LobbyCookieStatus getAccountCookieStatus(const std::string & accountID, std::chrono::seconds cookieLifetime); LobbyInviteStatus getAccountInviteStatus(const std::string & accountID, const std::string & roomID); LobbyRoomState getGameRoomStatus(const std::string & roomID); uint32_t getGameRoomFreeSlots(const std::string & roomID); bool isPlayerInGameRoom(const std::string & accountID); bool isPlayerInGameRoom(const std::string & accountID, const std::string & roomID); - bool isAccountExists(const std::string & accountID); + bool isAccountNameExists(const std::string & displayName); + bool isAccountIDExists(const std::string & accountID); }; diff --git a/lobby/LobbyDefines.h b/lobby/LobbyDefines.h index 370e25385..bcf02e187 100644 --- a/lobby/LobbyDefines.h +++ b/lobby/LobbyDefines.h @@ -26,7 +26,8 @@ struct LobbyGameRoom struct LobbyChatMessage { - std::string sender; + std::string accountID; + std::string displayName; std::string messageText; std::chrono::seconds age; }; diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index cc31d7120..9a1f21f6a 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -100,11 +100,13 @@ void LobbyServer::sendLoginFailed(const NetworkConnectionPtr & target, const std sendMessage(target, reply); } -void LobbyServer::sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie) +void LobbyServer::sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie, const std::string & displayName) { JsonNode reply; reply["type"].String() = "loginSuccess"; reply["accountCookie"].String() = accountCookie; + if (!displayName.empty()) + reply["displayName"].String() = displayName; sendMessage(target, reply); } @@ -117,8 +119,9 @@ void LobbyServer::sendChatHistory(const NetworkConnectionPtr & target, const std { JsonNode jsonEntry; + jsonEntry["accountID"].String() = message.accountID; + jsonEntry["displayName"].String() = message.displayName; jsonEntry["messageText"].String() = message.messageText; - jsonEntry["senderName"].String() = message.sender; jsonEntry["ageSeconds"].Integer() = message.age.count(); reply["messages"].Vector().push_back(jsonEntry); @@ -183,12 +186,13 @@ void LobbyServer::sendJoinRoomSuccess(const NetworkConnectionPtr & target, const sendMessage(target, reply); } -void LobbyServer::sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & senderName, const std::string & messageText) +void LobbyServer::sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & accountID, std::string & displayName, const std::string & messageText) { JsonNode reply; reply["type"].String() = "chatMessage"; reply["messageText"].String() = messageText; - reply["senderName"].String() = senderName; + reply["accountID"].String() = accountID; + reply["displayName"].String() = displayName; reply["roomMode"].String() = roomMode; reply["roomName"].String() = roomName; @@ -285,32 +289,33 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json) { - std::string senderName = activeAccounts[connection].accountID; + std::string accountID = activeAccounts[connection].accountID; std::string messageText = json["messageText"].String(); std::string messageTextClean = sanitizeChatMessage(messageText); + std::string displayName = database->getAccountDisplayName(accountID); if (messageTextClean.empty()) return; - database->insertChatMessage(senderName, "global", "english", messageText); + database->insertChatMessage(accountID, "global", "english", messageText); for(const auto & connection : activeAccounts) - sendChatMessage(connection.first, "global", "english", senderName, messageText); + sendChatMessage(connection.first, "global", "english", accountID, displayName, messageText); } void LobbyServer::receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json) { - std::string accountID = json["accountID"].String(); std::string displayName = json["displayName"].String(); std::string language = json["language"].String(); - if (database->isAccountExists(accountID)) - return sendLoginFailed(connection, "Account name already in use"); - - if (isAccountNameValid(accountID)) + if (isAccountNameValid(displayName)) return sendLoginFailed(connection, "Illegal account name"); + if (database->isAccountNameExists(displayName)) + return sendLoginFailed(connection, "Account name already in use"); + std::string accountCookie = boost::uuids::to_string(boost::uuids::random_generator()()); + std::string accountID = boost::uuids::to_string(boost::uuids::random_generator()()); database->insertAccount(accountID, displayName); database->insertAccessCookie(accountID, accountCookie); @@ -325,7 +330,7 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co std::string language = json["language"].String(); std::string version = json["version"].String(); - if (!database->isAccountExists(accountID)) + if (!database->isAccountIDExists(accountID)) return sendLoginFailed(connection, "Account not found"); auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime); @@ -344,7 +349,7 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co activeAccounts[connection].version = version; activeAccounts[connection].language = language; - sendLoginSuccess(connection, accountCookie); + sendLoginSuccess(connection, accountCookie, displayName); sendChatHistory(connection, database->getRecentMessageHistory()); // send active accounts list to new account @@ -369,7 +374,7 @@ void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, co { database->insertGameRoom(gameRoomID, accountID); activeGameRooms[connection].roomID = gameRoomID; - sendLoginSuccess(connection, accountCookie); + sendLoginSuccess(connection, accountCookie, {}); } } diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index c76b4f9ec..7262db0af 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -70,10 +70,10 @@ class LobbyServer : public INetworkServerListener void broadcastActiveAccounts(); void broadcastActiveGameRooms(); - void sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & senderName, const std::string & messageText); + void sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & accountID, std::string & displayName, const std::string & messageText); void sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie); void sendLoginFailed(const NetworkConnectionPtr & target, const std::string & reason); - void sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie); + void sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie, const std::string & displayName); void sendChatHistory(const NetworkConnectionPtr & target, const std::vector &); void sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID); void sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID); From ffa58152aca63d3a9a2419dbc7224de6937b5694 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 10 Jan 2024 15:31:11 +0200 Subject: [PATCH 29/98] Client-side support for hosting game server via lobby --- Mods/vcmi/config/vcmi/english.json | 14 ++ client/CMT.cpp | 42 ----- client/CMakeLists.txt | 2 + client/CServerHandler.cpp | 32 ++-- client/CServerHandler.h | 4 +- client/NetPacksLobbyClient.cpp | 9 ++ client/globalLobby/GlobalLobbyLoginWindow.cpp | 3 +- client/globalLobby/GlobalLobbyServerSetup.cpp | 144 ++++++++++++++++++ client/globalLobby/GlobalLobbyServerSetup.h | 49 ++++++ client/globalLobby/GlobalLobbyWindow.cpp | 3 + client/mainmenu/CMainMenu.cpp | 11 +- config/schemas/settings.json | 17 ++- config/widgets/lobbyWindow.json | 42 ++--- server/CVCMIServer.cpp | 19 +-- 14 files changed, 283 insertions(+), 108 deletions(-) create mode 100644 client/globalLobby/GlobalLobbyServerSetup.cpp create mode 100644 client/globalLobby/GlobalLobbyServerSetup.h diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 1431dab4d..5d88ee8ad 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -77,6 +77,20 @@ "vcmi.lobby.login.connecting" : "Connecting...", "vcmi.lobby.login.error" : "Connection error: %s", + "vcmi.lobby.room.create" : "Create Room", + "vcmi.lobby.room.players.limit" : "Players Limit", + "vcmi.lobby.room.public" : "Public", + "vcmi.lobby.room.private" : "Private", + "vcmi.lobby.room.description.public" : "Any player can join public room.", + "vcmi.lobby.room.description.private" : "Only invited players can join private room.", + "vcmi.lobby.room.description.new" : "To start the game, select a scenario or set up a random map.", + "vcmi.lobby.room.description.load" : "To start the game, use one of your saved games.", + "vcmi.lobby.room.description.limit" : "Up to %d players can enter your room, including you.", + "vcmi.lobby.room.new" : "New Game", + "vcmi.lobby.room.load" : "Load Game", + "vcmi.lobby.room.type" : "Room Type", + "vcmi.lobby.room.mode" : "Game Mode", + "vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.", "vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.", "vcmi.server.errors.modsToEnable" : "{Following mods are required}", diff --git a/client/CMT.cpp b/client/CMT.cpp index 05cab7883..32f5b3f35 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -139,14 +139,6 @@ int main(int argc, char * argv[]) ("donotstartserver,d","do not attempt to start server and just connect to it instead server") ("serverport", po::value(), "override port specified in config file") ("savefrequency", po::value(), "limit auto save creation to each N days") - ("lobby", "parameters address, port, uuid to connect ro remote lobby session") - ("lobby-address", po::value(), "address to remote lobby") - ("lobby-port", po::value(), "port to remote lobby") - ("lobby-host", "if this client hosts session") - ("lobby-uuid", po::value(), "uuid to the server") - ("lobby-connections", po::value(), "connections of server") - ("lobby-username", po::value(), "player name") - ("lobby-gamemode", po::value(), "use 0 for new game and 1 for load game") ("uuid", po::value(), "uuid for the client"); if(argc > 1) @@ -371,40 +363,6 @@ int main(int argc, char * argv[]) } std::vector names; - session["lobby"].Bool() = false; - if(vm.count("lobby")) - { - session["lobby"].Bool() = true; - session["host"].Bool() = false; - session["address"].String() = vm["lobby-address"].as(); - if(vm.count("lobby-username")) - session["username"].String() = vm["lobby-username"].as(); - else - session["username"].String() = settings["launcher"]["lobbyUsername"].String(); - if(vm.count("lobby-gamemode")) - session["gamemode"].Integer() = vm["lobby-gamemode"].as(); - else - session["gamemode"].Integer() = 0; - CSH->uuid = vm["uuid"].as(); - session["port"].Integer() = vm["lobby-port"].as(); - logGlobal->info("Remote lobby mode at %s:%d, uuid is %s", session["address"].String(), session["port"].Integer(), CSH->uuid); - if(vm.count("lobby-host")) - { - session["host"].Bool() = true; - session["hostConnections"].String() = std::to_string(vm["lobby-connections"].as()); - session["hostUuid"].String() = vm["lobby-uuid"].as(); - logGlobal->info("This client will host session, server uuid is %s", session["hostUuid"].String()); - } - - //we should not reconnect to previous game in online mode - Settings saveSession = settings.write["server"]["reconnect"]; - saveSession->Bool() = false; - - //start lobby immediately - names.push_back(session["username"].String()); - ESelectionScreen sscreen = session["gamemode"].Integer() == 0 ? ESelectionScreen::newGame : ESelectionScreen::loadGame; - CMM->openLobby(sscreen, session["host"].Bool(), &names, ELoadMode::MULTI); - } if(!settings["session"]["headless"].Bool()) { diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 7d00fee8c..507f6012b 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -97,6 +97,7 @@ set(client_SRCS globalLobby/GlobalLobbyClient.cpp globalLobby/GlobalLobbyLoginWindow.cpp + globalLobby/GlobalLobbyServerSetup.cpp globalLobby/GlobalLobbyWidget.cpp globalLobby/GlobalLobbyWindow.cpp @@ -277,6 +278,7 @@ set(client_HEADERS globalLobby/GlobalLobbyClient.h globalLobby/GlobalLobbyLoginWindow.h + globalLobby/GlobalLobbyServerSetup.h globalLobby/GlobalLobbyWidget.h globalLobby/GlobalLobbyWindow.h diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 165c6f902..8983c9db1 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -182,7 +182,7 @@ GlobalLobbyClient & CServerHandler::getGlobalLobby() return *lobbyClient; } -void CServerHandler::startLocalServerAndConnect() +void CServerHandler::startLocalServerAndConnect(bool connectToLobby) { if(threadRunLocalServer) threadRunLocalServer->join(); @@ -191,14 +191,10 @@ void CServerHandler::startLocalServerAndConnect() #if defined(SINGLE_PROCESS_APP) boost::condition_variable cond; - std::vector args{"--uuid=" + uuid, "--port=" + std::to_string(getLocalPort())}; - if(settings["session"]["lobby"].Bool() && settings["session"]["host"].Bool()) - { - args.push_back("--lobby=" + settings["session"]["address"].String()); - args.push_back("--connections=" + settings["session"]["hostConnections"].String()); - args.push_back("--lobby-port=" + std::to_string(settings["session"]["port"].Integer())); - args.push_back("--lobby-uuid=" + settings["session"]["hostUuid"].String()); - } + std::vector args{"--port=" + std::to_string(getLocalPort())}; + if(connectToLobby) + args.push_back("--lobby"); + threadRunLocalServer = std::make_unique([&cond, args, this] { setThreadName("CVCMIServer"); CVCMIServer::create(&cond, args); @@ -211,7 +207,7 @@ void CServerHandler::startLocalServerAndConnect() envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "startServer", true); } #else - threadRunLocalServer = std::make_unique(&CServerHandler::threadRunServer, this); //runs server executable; + threadRunLocalServer = std::make_unique(&CServerHandler::threadRunServer, this, connectToLobby); //runs server executable; #endif logNetwork->trace("Setting up thread calling server: %d ms", th->getDiff()); @@ -800,7 +796,7 @@ void CServerHandler::debugStartTest(std::string filename, bool save) if(settings["session"]["donotstartserver"].Bool()) connectToServer(getLocalHostname(), getLocalPort()); else - startLocalServerAndConnect(); + startLocalServerAndConnect(false); boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); @@ -916,22 +912,16 @@ void CServerHandler::visitForClient(CPackForClient & clientPack) client->handlePack(&clientPack); } -void CServerHandler::threadRunServer() +void CServerHandler::threadRunServer(bool connectToLobby) { #if !defined(VCMI_MOBILE) setThreadName("runServer"); const std::string logName = (VCMIDirs::get().userLogsPath() / "server_log.txt").string(); std::string comm = VCMIDirs::get().serverPath().string() + " --port=" + std::to_string(getLocalPort()) - + " --run-by-client" - + " --uuid=" + uuid; - if(settings["session"]["lobby"].Bool() && settings["session"]["host"].Bool()) - { - comm += " --lobby=" + settings["session"]["address"].String(); - comm += " --connections=" + settings["session"]["hostConnections"].String(); - comm += " --lobby-port=" + std::to_string(settings["session"]["port"].Integer()); - comm += " --lobby-uuid=" + settings["session"]["hostUuid"].String(); - } + + " --run-by-client"; + if(connectToLobby) + comm += " --lobby"; comm += " > \"" + logName + '\"'; logGlobal->info("Server command line: %s", comm); diff --git a/client/CServerHandler.h b/client/CServerHandler.h index bca2f4608..2d0ea27b3 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -94,7 +94,7 @@ class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClien std::shared_ptr highScoreCalc; void threadRunNetwork(); - void threadRunServer(); + void threadRunServer(bool connectToLobby); void onServerFinished(); void sendLobbyPack(const CPackForLobby & pack) const override; @@ -137,7 +137,7 @@ public: ~CServerHandler(); void resetStateForLobby(const StartInfo::EMode mode, const std::vector * names = nullptr); - void startLocalServerAndConnect(); + void startLocalServerAndConnect(bool connectToLobby); void connectToServer(const std::string & addr, const ui16 port); GlobalLobbyClient & getGlobalLobby(); diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index e28fb4682..578e28a1c 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -19,6 +19,7 @@ #include "lobby/ExtraOptionsTab.h" #include "lobby/SelectionTab.h" #include "lobby/CBonusSelection.h" +#include "globalLobby/GlobalLobbyWindow.h" #include "CServerHandler.h" #include "CGameInfo.h" @@ -40,12 +41,20 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon { handler.c->connectionID = pack.clientId; if(handler.mapToStart) + { handler.setMapInfo(handler.mapToStart); + } else if(!settings["session"]["headless"].Bool()) { if (GH.windows().topWindow()) GH.windows().popWindows(1); + while (!GH.windows().findWindows().empty()) + { + // if global lobby is open, pop all dialogs on top of it as well as lobby itself + GH.windows().popWindows(1); + } + GH.windows().createAndPushWindow(static_cast(handler.screenType)); } handler.state = EClientState::LOBBY; diff --git a/client/globalLobby/GlobalLobbyLoginWindow.cpp b/client/globalLobby/GlobalLobbyLoginWindow.cpp index 783cdb9ba..dce37c03a 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.cpp +++ b/client/globalLobby/GlobalLobbyLoginWindow.cpp @@ -66,7 +66,8 @@ void GlobalLobbyLoginWindow::onLogin() config->String() = inputUsername->getText(); labelStatus->setText(CGI->generaltexth->translate("vcmi.lobby.login.connecting")); - CSH->getGlobalLobby().connect(); + if (!CSH->getGlobalLobby().isConnected()) + CSH->getGlobalLobby().connect(); buttonClose->block(true); } diff --git a/client/globalLobby/GlobalLobbyServerSetup.cpp b/client/globalLobby/GlobalLobbyServerSetup.cpp new file mode 100644 index 000000000..d2f0f2ffd --- /dev/null +++ b/client/globalLobby/GlobalLobbyServerSetup.cpp @@ -0,0 +1,144 @@ +/* + * GlobalLobbyServerSetup.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "GlobalLobbyServerSetup.h" + +#include "../gui/CGuiHandler.h" +#include "../widgets/TextControls.h" +#include "../widgets/Images.h" +#include "../widgets/Buttons.h" +#include "../CGameInfo.h" +#include "../CServerHandler.h" +#include "../mainmenu/CMainMenu.h" + +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/MetaString.h" +#include "../../lib/CConfigHandler.h" + +GlobalLobbyServerSetup::GlobalLobbyServerSetup() + : CWindowObject(BORDERED) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + + pos.w = 284; + pos.h = 340; + + background = std::make_shared(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h)); + labelTitle = std::make_shared( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.create")); + labelPlayerLimit = std::make_shared( pos.w / 2, 48, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.players.limit")); + labelRoomType = std::make_shared( pos.w / 2, 108, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.type")); + labelGameMode = std::make_shared( pos.w / 2, 158, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.mode")); + + togglePlayerLimit = std::make_shared(nullptr); + togglePlayerLimit->addToggle(2, std::make_shared(Point(10 + 39*0, 60), AnimationPath::builtin("RanNum2"), CButton::tooltip(), 0)); + togglePlayerLimit->addToggle(3, std::make_shared(Point(10 + 39*1, 60), AnimationPath::builtin("RanNum3"), CButton::tooltip(), 0)); + togglePlayerLimit->addToggle(4, std::make_shared(Point(10 + 39*2, 60), AnimationPath::builtin("RanNum4"), CButton::tooltip(), 0)); + togglePlayerLimit->addToggle(5, std::make_shared(Point(10 + 39*3, 60), AnimationPath::builtin("RanNum5"), CButton::tooltip(), 0)); + togglePlayerLimit->addToggle(6, std::make_shared(Point(10 + 39*4, 60), AnimationPath::builtin("RanNum6"), CButton::tooltip(), 0)); + togglePlayerLimit->addToggle(7, std::make_shared(Point(10 + 39*5, 60), AnimationPath::builtin("RanNum7"), CButton::tooltip(), 0)); + togglePlayerLimit->addToggle(8, std::make_shared(Point(10 + 39*6, 60), AnimationPath::builtin("RanNum8"), CButton::tooltip(), 0)); + togglePlayerLimit->setSelected(settings["lobby"]["roomPlayerLimit"].Integer()); + togglePlayerLimit->addCallback([this](int index){onPlayerLimitChanged(index);}); + + auto buttonPublic = std::make_shared(Point(10, 120), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0); + auto buttonPrivate = std::make_shared(Point(146, 120), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0); + buttonPublic->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.public"), EFonts::FONT_SMALL, Colors::YELLOW); + buttonPrivate->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.private"), EFonts::FONT_SMALL, Colors::YELLOW); + + toggleRoomType = std::make_shared(nullptr); + toggleRoomType->addToggle(0, buttonPublic); + toggleRoomType->addToggle(1, buttonPrivate); + toggleRoomType->setSelected(settings["lobby"]["roomType"].Integer()); + toggleRoomType->addCallback([this](int index){onRoomTypeChanged(index);}); + + auto buttonNewGame = std::make_shared(Point(10, 170), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0); + auto buttonLoadGame = std::make_shared(Point(146, 170), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0); + buttonNewGame->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.new"), EFonts::FONT_SMALL, Colors::YELLOW); + buttonLoadGame->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.load"), EFonts::FONT_SMALL, Colors::YELLOW); + + toggleGameMode = std::make_shared(nullptr); + toggleGameMode->addToggle(0, buttonNewGame); + toggleGameMode->addToggle(1, buttonLoadGame); + toggleGameMode->setSelected(settings["lobby"]["roomMode"].Integer()); + toggleGameMode->addCallback([this](int index){onGameModeChanged(index);}); + + labelDescription = std::make_shared("", Rect(10, 195, pos.w - 20, 80), 1, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); + + buttonCreate = std::make_shared(Point(10, 300), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onCreate(); }); + buttonClose = std::make_shared(Point(210, 300), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }); + + background->playerColored(PlayerColor(1)); + + updateDescription(); + center(); +} + +void GlobalLobbyServerSetup::updateDescription() +{ + MetaString description; + description.appendRawString("%s %s %s"); + if (toggleRoomType->getSelected() == 0) + description.replaceTextID("vcmi.lobby.room.description.public"); + else + description.replaceTextID("vcmi.lobby.room.description.private"); + + if (toggleGameMode->getSelected() == 0) + description.replaceTextID("vcmi.lobby.room.description.new"); + else + description.replaceTextID("vcmi.lobby.room.description.load"); + + description.replaceTextID("vcmi.lobby.room.description.limit"); + description.replaceNumber(togglePlayerLimit->getSelected()); + + labelDescription->setText(description.toString()); +} + +void GlobalLobbyServerSetup::onPlayerLimitChanged(int value) +{ + Settings config = settings.write["lobby"]["roomPlayerLimit"]; + config->Integer() = value; + updateDescription(); +} + +void GlobalLobbyServerSetup::onRoomTypeChanged(int value) +{ + Settings config = settings.write["lobby"]["roomType"]; + config->Integer() = value; + updateDescription(); +} + +void GlobalLobbyServerSetup::onGameModeChanged(int value) +{ + Settings config = settings.write["lobby"]["roomMode"]; + config->Integer() = value; + updateDescription(); +} + +void GlobalLobbyServerSetup::onCreate() +{ + if (toggleGameMode->getSelected() == 0) + { + CSH->resetStateForLobby(StartInfo::NEW_GAME, nullptr); + CSH->screenType = ESelectionScreen::newGame; + } + else + { + CSH->resetStateForLobby(StartInfo::LOAD_GAME, nullptr); + CSH->screenType = ESelectionScreen::loadGame; + } + CSH->loadMode = ELoadMode::MULTI; + CSH->startLocalServerAndConnect(true); +} + +void GlobalLobbyServerSetup::onClose() +{ + close(); +} diff --git a/client/globalLobby/GlobalLobbyServerSetup.h b/client/globalLobby/GlobalLobbyServerSetup.h new file mode 100644 index 000000000..7f49bb130 --- /dev/null +++ b/client/globalLobby/GlobalLobbyServerSetup.h @@ -0,0 +1,49 @@ +/* + * GlobalLobbyServerSetup.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../windows/CWindowObject.h" + +class CLabel; +class CTextBox; +class FilledTexturePlayerColored; +class CButton; +class CToggleGroup; + +class GlobalLobbyServerSetup : public CWindowObject +{ + std::shared_ptr background; + std::shared_ptr labelTitle; + + std::shared_ptr labelPlayerLimit; + std::shared_ptr labelRoomType; + std::shared_ptr labelGameMode; + + std::shared_ptr togglePlayerLimit; // 2-8 + std::shared_ptr toggleRoomType; // public or private + std::shared_ptr toggleGameMode; // new game or load game + + std::shared_ptr labelDescription; + std::shared_ptr labelStatus; + + std::shared_ptr buttonCreate; + std::shared_ptr buttonClose; + + void updateDescription(); + void onPlayerLimitChanged(int value); + void onRoomTypeChanged(int value); + void onGameModeChanged(int value); + + void onCreate(); + void onClose(); + +public: + GlobalLobbyServerSetup(); +}; diff --git a/client/globalLobby/GlobalLobbyWindow.cpp b/client/globalLobby/GlobalLobbyWindow.cpp index c4b5f7fb0..9f91d0ccc 100644 --- a/client/globalLobby/GlobalLobbyWindow.cpp +++ b/client/globalLobby/GlobalLobbyWindow.cpp @@ -13,8 +13,10 @@ #include "GlobalLobbyWidget.h" #include "GlobalLobbyClient.h" +#include "GlobalLobbyServerSetup.h" #include "../gui/CGuiHandler.h" +#include "../gui/WindowHandler.h" #include "../widgets/TextControls.h" #include "../CServerHandler.h" @@ -47,6 +49,7 @@ void GlobalLobbyWindow::doSendChatMessage() void GlobalLobbyWindow::doCreateGameRoom() { + GH.windows().createAndPushWindow(); // TODO: // start local server and supply our UUID / client credentials to it // server logs into lobby ( uuid = client, mode = server ). This creates 'room' in mode 'empty' diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index c7e3fdcb5..911fe7f25 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -458,10 +458,11 @@ CMultiMode::CMultiMode(ESelectionScreen ScreenType) playerName->setText(getPlayerName()); playerName->cb += std::bind(&CMultiMode::onNameChange, this, _1); - buttonHotseat = std::make_shared(Point(373, 78), AnimationPath::builtin("MUBHOT.DEF"), CGI->generaltexth->zelp[266], std::bind(&CMultiMode::hostTCP, this)); - buttonHost = std::make_shared(Point(373, 78 + 57 * 1), AnimationPath::builtin("MUBHOST.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.hostTCP"), ""), std::bind(&CMultiMode::hostTCP, this)); - buttonJoin = std::make_shared(Point(373, 78 + 57 * 2), AnimationPath::builtin("MUBJOIN.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.joinTCP"), ""), std::bind(&CMultiMode::joinTCP, this)); - buttonLobby = std::make_shared(Point(373, 78 + 57 * 4), AnimationPath::builtin("MUBONL.DEF"), CGI->generaltexth->zelp[265], std::bind(&CMultiMode::openLobby, this)); + buttonHotseat = std::make_shared(Point(373, 78 + 57 * 0), AnimationPath::builtin("MUBHOT.DEF"), CGI->generaltexth->zelp[266], std::bind(&CMultiMode::hostTCP, this)); + buttonLobby = std::make_shared(Point(373, 78 + 57 * 1), AnimationPath::builtin("MUBONL.DEF"), CGI->generaltexth->zelp[265], std::bind(&CMultiMode::openLobby, this)); + + buttonHost = std::make_shared(Point(373, 78 + 57 * 3), AnimationPath::builtin("MUBHOST.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.hostTCP"), ""), std::bind(&CMultiMode::hostTCP, this)); + buttonJoin = std::make_shared(Point(373, 78 + 57 * 4), AnimationPath::builtin("MUBJOIN.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.joinTCP"), ""), std::bind(&CMultiMode::joinTCP, this)); buttonCancel = std::make_shared(Point(373, 424), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[288], [=](){ close();}, EShortcut::GLOBAL_CANCEL); } @@ -617,7 +618,7 @@ void CSimpleJoinScreen::startConnection(const std::string & addr, ui16 port) #endif if(addr.empty()) - CSH->startLocalServerAndConnect(); + CSH->startLocalServerAndConnect(false); else CSH->connectToServer(addr, port); } diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 73d7b6df6..daf7cb301 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -553,7 +553,7 @@ "type" : "object", "additionalProperties" : false, "default" : {}, - "required" : [ "mapPreview", "accountID", "accountCookie", "displayName", "hostname", "port" ], + "required" : [ "mapPreview", "accountID", "accountCookie", "displayName", "hostname", "port", "roomPlayerLimit", "roomType", "roomMode" ], "properties" : { "mapPreview" : { "type" : "boolean", @@ -583,6 +583,21 @@ "type" : "number", "default" : 30303 }, + + "roomPlayerLimit" : { + "type" : "number", + "default" : 2 + }, + + "roomType" : { + "type" : "number", + "default" : 0 + }, + + "roomMode" : { + "type" : "number", + "default" : 0 + }, } }, "gameTweaks" : { diff --git a/config/widgets/lobbyWindow.json b/config/widgets/lobbyWindow.json index 2b81f3dbe..dfbf90d00 100644 --- a/config/widgets/lobbyWindow.json +++ b/config/widgets/lobbyWindow.json @@ -58,7 +58,7 @@ { "type": "areaFilled", - "rect": {"x": 5, "y": 210, "w": 250, "h": 310} + "rect": {"x": 5, "y": 210, "w": 250, "h": 340} }, { "type": "labelTitle", @@ -115,6 +115,24 @@ "text" : "Player List" }, + { + "type": "button", + "position": {"x": 840, "y": 10}, + "image": "settingsWindow/button80", + "help": "core.help.288", + "callback": "closeWindow", + "items": + [ + { + "type": "label", + "font": "medium", + "alignment": "center", + "color": "yellow", + "text": "Leave" + } + ] + }, + { "type": "button", "position": {"x": 940, "y": 10}, @@ -129,7 +147,7 @@ "font": "medium", "alignment": "center", "color": "yellow", - "text": "Exit" + "text": "Close" } ] }, @@ -153,24 +171,6 @@ ] }, - { - "type": "button", - "position": {"x": 10, "y": 520}, - "image": "settingsWindow/button190", - "help": "core.help.288", - "callback": "createGameRoom", - "items": - [ - { - "type": "label", - "font": "medium", - "alignment": "center", - "color": "yellow", - "text": "Start Public Game" - } - ] - }, - { "type": "button", "position": {"x": 10, "y": 555}, @@ -184,7 +184,7 @@ "font": "medium", "alignment": "center", "color": "yellow", - "text": "Start Private Game" + "text": "Create Room" } ] }, diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 2f22b4c80..bb677deae 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -266,17 +266,11 @@ void CVCMIServer::establishOutgoingConnection() if(!cmdLineOptions.count("lobby")) return; - uuid = cmdLineOptions["lobby-uuid"].as(); - auto address = cmdLineOptions["lobby"].as(); - int port = cmdLineOptions["lobby-port"].as(); - logNetwork->info("Establishing connection to remote at %s:%d with uuid %s", address, port, uuid); + std::string hostname = settings["lobby"]["hostname"].String(); + int16_t port = settings["lobby"]["port"].Integer(); outgoingConnection = std::make_unique(*this); - - outgoingConnection->start(address, port);//, SERVER_NAME, uuid); - -// connections.insert(c); -// remoteConnections.insert(c); + outgoingConnection->start(hostname, port); } void CVCMIServer::prepareToRestart() @@ -1004,12 +998,7 @@ static void handleCommandOptions(int argc, const char * argv[], boost::program_o ("help,h", "display help and exit") ("version,v", "display version information and exit") ("run-by-client", "indicate that server launched by client on same machine") - ("uuid", po::value(), "") - ("port", po::value(), "port at which server will listen to connections from client") - ("lobby", po::value(), "address to remote lobby") - ("lobby-port", po::value(), "port at which server connect to remote lobby") - ("lobby-uuid", po::value(), "") - ("connections", po::value(), "amount of connections to remote lobby"); + ("port", po::value(), "port at which server will listen to connections from client"); if(argc > 1) { From c4db99a60daa4e5d6377b0d99cf4aefe15470f83 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 11 Jan 2024 22:33:58 +0200 Subject: [PATCH 30/98] Removed old in-launcher lobby --- launcher/CMakeLists.txt | 11 - launcher/lobby/chat_moc.cpp | 173 ------- launcher/lobby/chat_moc.h | 70 --- launcher/lobby/chat_moc.ui | 121 ----- launcher/lobby/lobby.cpp | 123 ----- launcher/lobby/lobby.h | 226 --------- launcher/lobby/lobby_moc.cpp | 585 ------------------------ launcher/lobby/lobby_moc.h | 92 ---- launcher/lobby/lobby_moc.ui | 311 ------------- launcher/lobby/lobbyroomrequest_moc.cpp | 59 --- launcher/lobby/lobbyroomrequest_moc.h | 38 -- launcher/lobby/lobbyroomrequest_moc.ui | 151 ------ launcher/mainwindow_moc.cpp | 13 - launcher/mainwindow_moc.h | 6 +- launcher/mainwindow_moc.ui | 57 --- launcher/translation/chinese.ts | 161 +------ launcher/translation/czech.ts | 161 +------ launcher/translation/english.ts | 161 +------ launcher/translation/french.ts | 159 +------ launcher/translation/german.ts | 159 +------ launcher/translation/polish.ts | 161 +------ launcher/translation/russian.ts | 161 +------ launcher/translation/spanish.ts | 161 +------ launcher/translation/ukrainian.ts | 159 +------ launcher/translation/vietnamese.ts | 161 +------ 25 files changed, 79 insertions(+), 3561 deletions(-) delete mode 100644 launcher/lobby/chat_moc.cpp delete mode 100644 launcher/lobby/chat_moc.h delete mode 100644 launcher/lobby/chat_moc.ui delete mode 100644 launcher/lobby/lobby.cpp delete mode 100644 launcher/lobby/lobby.h delete mode 100644 launcher/lobby/lobby_moc.cpp delete mode 100644 launcher/lobby/lobby_moc.h delete mode 100644 launcher/lobby/lobby_moc.ui delete mode 100644 launcher/lobby/lobbyroomrequest_moc.cpp delete mode 100644 launcher/lobby/lobbyroomrequest_moc.h delete mode 100644 launcher/lobby/lobbyroomrequest_moc.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 613ebecfe..f55f8d32c 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -15,10 +15,6 @@ set(launcher_SRCS launcherdirs.cpp jsonutils.cpp updatedialog_moc.cpp - lobby/lobby.cpp - lobby/lobby_moc.cpp - lobby/lobbyroomrequest_moc.cpp - lobby/chat_moc.cpp ) set(launcher_HEADERS @@ -37,10 +33,6 @@ set(launcher_HEADERS launcherdirs.h jsonutils.h updatedialog_moc.h - lobby/lobby.h - lobby/lobby_moc.h - lobby/lobbyroomrequest_moc.h - lobby/chat_moc.h main.h ) @@ -52,9 +44,6 @@ set(launcher_FORMS firstLaunch/firstlaunch_moc.ui mainwindow_moc.ui updatedialog_moc.ui - lobby/lobby_moc.ui - lobby/lobbyroomrequest_moc.ui - lobby/chat_moc.ui ) set(launcher_TS diff --git a/launcher/lobby/chat_moc.cpp b/launcher/lobby/chat_moc.cpp deleted file mode 100644 index a260ba312..000000000 --- a/launcher/lobby/chat_moc.cpp +++ /dev/null @@ -1,173 +0,0 @@ -/* - * chat_moc.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "chat_moc.h" -#include "ui_chat_moc.h" - -Chat::Chat(QWidget *parent) : - QWidget(parent), - ui(new Ui::Chat) -{ - ui->setupUi(this); - - namesCompleter.setModel(ui->listUsers->model()); - namesCompleter.setCompletionMode(QCompleter::InlineCompletion); - - ui->messageEdit->setCompleter(&namesCompleter); - - for([[maybe_unused]] auto i : {GLOBAL, ROOM}) - chatDocuments.push_back(new QTextDocument(this)); - - setChatId(GLOBAL); -} - -Chat::~Chat() -{ - delete ui; -} - -void Chat::setUsername(const QString & user) -{ - username = user; -} - -void Chat::setSession(const QString & s) -{ - session = s; - - on_chatSwitch_clicked(); -} - -void Chat::setChannel(const QString & channel) -{ - static const QMap chatNames{{"global", GLOBAL}, {"room", ROOM}}; - - setChatId(chatNames.value(channel)); -} - -void Chat::addUser(const QString & user) -{ - ui->listUsers->addItem(new QListWidgetItem("@" + user)); -} - -void Chat::clearUsers() -{ - ui->listUsers->clear(); -} - -void Chat::chatMessage(const QString & title, const QString & channel, QString body, bool isSystem) -{ - const QTextCharFormat regularFormat; - const QString boldHtml = "%1"; - const QString colorHtml = "%2"; - bool meMentioned = false; - bool isScrollBarBottom = (ui->chat->verticalScrollBar()->maximum() - ui->chat->verticalScrollBar()->value() < 24); - - static const QMap chatNames{{"global", GLOBAL}, {"room", ROOM}}; - QTextDocument * doc = ui->chat->document(); - if(chatNames.contains(channel)) - doc = chatDocuments[chatNames.value(channel)]; - - QTextCursor curs(doc); - curs.movePosition(QTextCursor::End); - - QString titleColor = "Olive"; - if(isSystem || title == "System") - titleColor = "ForestGreen"; - if(title == username) - titleColor = "Gold"; - - curs.insertHtml(boldHtml.arg(colorHtml.arg(titleColor, title + ": "))); - - QRegularExpression mentionRe("@[\\w\\d]+"); - auto subBody = body; - int mem = 0; - for(auto match = mentionRe.match(subBody); match.hasMatch(); match = mentionRe.match(subBody)) - { - body.insert(mem + match.capturedEnd(), QChar(-1)); - body.insert(mem + match.capturedStart(), QChar(-1)); - mem += match.capturedEnd() + 2; - subBody = body.right(body.size() - mem); - } - auto pieces = body.split(QChar(-1)); - for(auto & block : pieces) - { - if(block.startsWith("@")) - { - if(block == "@" + username) - { - meMentioned = true; - curs.insertHtml(boldHtml.arg(colorHtml.arg("IndianRed", block))); - } - else - curs.insertHtml(colorHtml.arg("DeepSkyBlue", block)); - } - else - { - if(isSystem) - curs.insertHtml(colorHtml.arg("ForestGreen", block)); - else - curs.insertText(block, regularFormat); - } - } - curs.insertText("\n", regularFormat); - - if(doc == ui->chat->document() && (meMentioned || isScrollBarBottom)) - { - ui->chat->ensureCursorVisible(); - ui->chat->verticalScrollBar()->setValue(ui->chat->verticalScrollBar()->maximum()); - } -} - -void Chat::chatMessage(const QString & title, QString body, bool isSystem) -{ - chatMessage(title, "", body, isSystem); -} - -void Chat::sysMessage(QString body) -{ - chatMessage("System", body, true); -} - -void Chat::sendMessage() -{ - QString msg(ui->messageEdit->text()); - ui->messageEdit->clear(); - emit messageSent(msg); -} - -void Chat::on_messageEdit_returnPressed() -{ - sendMessage(); -} - -void Chat::on_sendButton_clicked() -{ - sendMessage(); -} - -void Chat::on_chatSwitch_clicked() -{ - static const QMap chatNames{{GLOBAL, "global"}, {ROOM, "room"}}; - - if(chatId == GLOBAL && !session.isEmpty()) - emit channelSwitch(chatNames[ROOM]); - else - emit channelSwitch(chatNames[GLOBAL]); -} - -void Chat::setChatId(ChatId _chatId) -{ - static const QMap chatNames{{GLOBAL, "Global"}, {ROOM, "Room"}}; - - chatId = _chatId; - ui->chatSwitch->setText(chatNames[chatId] + " chat"); - ui->chat->setDocument(chatDocuments[chatId]); -} diff --git a/launcher/lobby/chat_moc.h b/launcher/lobby/chat_moc.h deleted file mode 100644 index a796dc7a9..000000000 --- a/launcher/lobby/chat_moc.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * chat_moc.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include -#include - -namespace Ui { -class Chat; -} - -class Chat : public QWidget -{ - Q_OBJECT - - enum ChatId - { - GLOBAL = 0, - ROOM - }; - - QCompleter namesCompleter; - QString username; - QString session; - ChatId chatId = GLOBAL; - - QVector chatDocuments; - -private: - void setChatId(ChatId); - void sendMessage(); - -public: - explicit Chat(QWidget *parent = nullptr); - ~Chat(); - - void setUsername(const QString &); - void setSession(const QString &); - void setChannel(const QString &); - - void clearUsers(); - void addUser(const QString & user); - - void chatMessage(const QString & title, const QString & channel, QString body, bool isSystem = false); - void chatMessage(const QString & title, QString body, bool isSystem = false); - -signals: - void messageSent(QString); - void channelSwitch(QString); - -public slots: - void sysMessage(QString body); - -private slots: - void on_messageEdit_returnPressed(); - - void on_sendButton_clicked(); - - void on_chatSwitch_clicked(); - -private: - Ui::Chat *ui; -}; diff --git a/launcher/lobby/chat_moc.ui b/launcher/lobby/chat_moc.ui deleted file mode 100644 index a3208d982..000000000 --- a/launcher/lobby/chat_moc.ui +++ /dev/null @@ -1,121 +0,0 @@ - - - Chat - - - - 0 - 0 - 465 - 413 - - - - Form - - - - 2 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - Users in lobby - - - -1 - - - - - - - Global chat - - - - - - - - - - 0 - 0 - - - - - 16777215 - 96 - - - - 0 - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::NoSelection - - - true - - - QListView::SinglePass - - - - - - - - - - -1 - - - 0 - - - - - - - - type you message - - - - - - - send - - - - - - - - - - diff --git a/launcher/lobby/lobby.cpp b/launcher/lobby/lobby.cpp deleted file mode 100644 index 3e8c9648e..000000000 --- a/launcher/lobby/lobby.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * lobby.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "lobby.h" -#include "../lib/GameConstants.h" - -SocketLobby::SocketLobby(QObject *parent) : - QObject(parent) -{ - socket = new QTcpSocket(this); - connect(socket, SIGNAL(connected()), this, SLOT(connected())); - connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected())); - connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead())); - connect(socket, SIGNAL(bytesWritten(qint64)), this, SLOT(bytesWritten(qint64))); -} - -void SocketLobby::connectServer(const QString & host, int port, const QString & usr, int timeout) -{ - username = usr; - - socket->connectToHost(host, port); - - if(!socket->waitForDisconnected(timeout) && !isConnected) - { - emit text("Error: " + socket->errorString()); - emit disconnect(); - } -} - -void SocketLobby::disconnectServer() -{ - socket->disconnectFromHost(); -} - -void SocketLobby::requestNewSession(const QString & session, int totalPlayers, const QString & pswd, const QMap & mods) -{ - const QString sessionMessage = ProtocolStrings[CREATE].arg(session, pswd, QString::number(totalPlayers), prepareModsClientString(mods)); - send(sessionMessage); -} - -void SocketLobby::requestJoinSession(const QString & session, const QString & pswd, const QMap & mods) -{ - const QString sessionMessage = ProtocolStrings[JOIN].arg(session, pswd, prepareModsClientString(mods)); - send(sessionMessage); -} - -void SocketLobby::requestLeaveSession(const QString & session) -{ - const QString sessionMessage = ProtocolStrings[LEAVE].arg(session); - send(sessionMessage); -} - -void SocketLobby::requestReadySession(const QString & session) -{ - const QString sessionMessage = ProtocolStrings[READY].arg(session); - send(sessionMessage); -} - -void SocketLobby::send(const QString & msg) -{ - QByteArray str = msg.toUtf8(); - int sz = str.size(); - QByteArray pack((const char *)&sz, sizeof(sz)); - pack.append(str); - socket->write(pack); -} - -void SocketLobby::connected() -{ - isConnected = true; - emit text("Connected!"); - - QByteArray greetingBytes; - greetingBytes.append(ProtocolVersion); - greetingBytes.append(ProtocolEncoding.size()); - const QString greetingConst = QString(greetingBytes) - + ProtocolStrings[GREETING].arg(QString::fromStdString(ProtocolEncoding), - username, - QString::fromStdString(GameConstants::VCMI_VERSION)); - send(greetingConst); -} - -void SocketLobby::disconnected() -{ - isConnected = false; - emit disconnect(); - emit text("Disconnected!"); -} - -void SocketLobby::bytesWritten(qint64 bytes) -{ - qDebug() << "We wrote: " << bytes; -} - -void SocketLobby::readyRead() -{ - qDebug() << "Reading..."; - emit receive(socket->readAll()); -} - - -ServerCommand::ServerCommand(ProtocolConsts cmd, const QStringList & args): - command(cmd), - arguments(args) -{ -} - -QString prepareModsClientString(const QMap & mods) -{ - QStringList result; - for(auto & mod : mods.keys()) - { - result << mod + "&" + mods[mod]; - } - return result.join(";"); -} diff --git a/launcher/lobby/lobby.h b/launcher/lobby/lobby.h deleted file mode 100644 index 84fe67e22..000000000 --- a/launcher/lobby/lobby.h +++ /dev/null @@ -1,226 +0,0 @@ -/* - * lobby.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include -#include - -const unsigned int ProtocolVersion = 5; -const std::string ProtocolEncoding = "utf8"; - -class ProtocolError: public std::runtime_error -{ -public: - ProtocolError(const char * w): std::runtime_error(w) {} -}; - -enum ProtocolConsts -{ - //client consts - GREETING, USERNAME, MESSAGE, VERSION, CREATE, JOIN, LEAVE, KICK, READY, FORCESTART, HERE, ALIVE, HOSTMODE, SETCHANNEL, - - //server consts - SESSIONS, CREATED, JOINED, KICKED, SRVERROR, CHAT, CHATCHANNEL, START, STATUS, HOST, MODS, CLIENTMODS, USERS, HEALTH, GAMEMODE, CHANNEL -}; - -const QMap ProtocolStrings -{ - //=== client commands === - - //handshaking with server - //%1: first byte is protocol_version, then size of encoding string in bytes, then encoding string - //%2: client name - //%3: VCMI version - {GREETING, "%1%2%3"}, - - //[unsupported] autorization with username - //%1: username - {USERNAME, "%1"}, - - //sending message to the chat - //%1: message text - {MESSAGE, "%1"}, - - //create new room - //%1: room name - //%2: password for the room - //%3: max number of players - //%4: mods used by host - // each mod has a format modname&modversion, mods should be separated by ; symbol - {CREATE, "%1%2%3%4"}, - - //join to the room - //%1: room name - //%2: password for the room - //%3: list of mods used by player - // each mod has a format modname&modversion, mods should be separated by ; symbol - {JOIN, "%1%2%3"}, - - //leave the room - //%1: room name - {LEAVE, "%1"}, - - //kick user from the current room - //%1: player username - {KICK, "%1"}, - - //signal that player is ready for game - //%1: room name - {READY, "%1"}, - - //[unsupported] start session immediately - //%1: room name - {FORCESTART, "%1"}, - - //request user list - {HERE, ""}, - - //used as reponse to healcheck - {ALIVE, ""}, - - //host sets game mode (new game or load game) - //%1: game mode - 0 for new game, 1 for load game - {HOSTMODE, "%1"}, - - //set new chat channel - //%1: channel name - {SETCHANNEL, "%1"}, - - //=== server commands === - //server commands are started from :>>, arguments are enumerated by : symbol - - //new session was created - //arg[0]: room name - {CREATED, "CREATED"}, - - //list of existing sessions - //arg[0]: amount of sessions, following arguments depending on it - //arg[x]: session name - //arg[x+1]: amount of players in the session - //arg[x+2]: total amount of players allowed - //arg[x+3]: True if session is protected by password - {SESSIONS, "SESSIONS"}, - - //user has joined to the session - //arg[0]: session name - //arg[1]: username (who was joined) - {JOINED, "JOIN"}, - - //user has left the session - //arg[0]: session name - //arg[1]: username (who has left) - {KICKED, "KICK"}, - - //session has been started - //arg[0]: session name - //arg[1]: uuid to be used for connection - {START, "START"}, - - //host ownership for the game session - //arg[0]: uuid to be used by vcmiserver - //arg[1]: amount of players (clients) to be connected - {HOST, "HOST"}, - - //room status - //arg[0]: amount of players, following arguments depending on it - //arg[x]: player username - //arg[x+1]: True if player is ready - {STATUS, "STATUS"}, //joined_players:player_name:is_ready - - //server error - //arg[0]: error message - {SRVERROR, "ERROR"}, - - //mods used in the session by host player - //arg[0]: amount of mods, following arguments depending on it - //arg[x]: mod name - //arg[x+1]: mod version - {MODS, "MODS"}, - - //mods used by user - //arg[0]: username - //arg[1]: amount of mods, following arguments depending on it - //arg[x]: mod name - //arg[x+1]: mod version - {CLIENTMODS, "MODSOTHER"}, - - //received chat message - //arg[0]: sender username - //arg[1]: channel - //arg[2]: message text - {CHAT, "MSG"}, - - //received chat message to specific channel - //arg[0]: sender username - //arg[1]: channel - //arg[2]: message text - {CHATCHANNEL, "MSGCH"}, - - //list of users currently in lobby - //arg[0]: amount of players, following arguments depend on it - //arg[x]: username - //arg[x+1]: room (empty if not in the room) - {USERS, "USERS"}, - - //healthcheck from server - {HEALTH, "HEALTH"}, - - //game mode (new game or load game) set by host - //arg[0]: game mode - {GAMEMODE, "GAMEMODE"}, - - //chat channel changed - //arg[0]: channel name - {CHANNEL, "CHANNEL"}, -}; - -class ServerCommand -{ -public: - ServerCommand(ProtocolConsts, const QStringList & arguments); - - const ProtocolConsts command; - const QStringList arguments; -}; - -class SocketLobby : public QObject -{ - Q_OBJECT -public: - explicit SocketLobby(QObject *parent = nullptr); - void connectServer(const QString & host, int port, const QString & username, int timeout); - void disconnectServer(); - void requestNewSession(const QString & session, int totalPlayers, const QString & pswd, const QMap & mods); - void requestJoinSession(const QString & session, const QString & pswd, const QMap & mods); - void requestLeaveSession(const QString & session); - void requestReadySession(const QString & session); - - void send(const QString &); - -signals: - - void text(QString); - void receive(QString); - void disconnect(); - -public slots: - - void connected(); - void disconnected(); - void bytesWritten(qint64 bytes); - void readyRead(); - -private: - QTcpSocket *socket; - bool isConnected = false; - QString username; -}; - -QString prepareModsClientString(const QMap & mods); diff --git a/launcher/lobby/lobby_moc.cpp b/launcher/lobby/lobby_moc.cpp deleted file mode 100644 index 7c50fd912..000000000 --- a/launcher/lobby/lobby_moc.cpp +++ /dev/null @@ -1,585 +0,0 @@ -/* - * lobby_moc.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "main.h" -#include "lobby_moc.h" -#include "ui_lobby_moc.h" -#include "lobbyroomrequest_moc.h" -#include "../mainwindow_moc.h" -#include "../modManager/cmodlist.h" -#include "../../lib/CConfigHandler.h" - -enum GameMode -{ - NEW_GAME = 0, LOAD_GAME = 1 -}; - -enum ModResolutionRoles -{ - ModNameRole = Qt::UserRole + 1, - ModEnableRole, - ModResolvableRole -}; - -Lobby::Lobby(QWidget *parent) : - QWidget(parent), - ui(new Ui::Lobby) -{ - ui->setupUi(this); - - connect(&socketLobby, SIGNAL(text(QString)), ui->chatWidget, SLOT(sysMessage(QString))); - connect(&socketLobby, SIGNAL(receive(QString)), this, SLOT(dispatchMessage(QString))); - connect(&socketLobby, SIGNAL(disconnect()), this, SLOT(onDisconnected())); - connect(ui->chatWidget, SIGNAL(messageSent(QString)), this, SLOT(onMessageSent(QString))); - connect(ui->chatWidget, SIGNAL(channelSwitch(QString)), this, SLOT(onChannelSwitch(QString))); - - QString hostString("%1:%2"); - hostString = hostString.arg(QString::fromStdString(settings["launcher"]["lobbyUrl"].String())); - hostString = hostString.arg(settings["launcher"]["lobbyPort"].Integer()); - - ui->serverEdit->setText(hostString); - ui->userEdit->setText(QString::fromStdString(settings["launcher"]["lobbyUsername"].String())); - ui->kickButton->setVisible(false); -} - -void Lobby::changeEvent(QEvent *event) -{ - if(event->type() == QEvent::LanguageChange) - { - ui->retranslateUi(this); - } - QWidget::changeEvent(event); -} - -Lobby::~Lobby() -{ - delete ui; -} - -QMap Lobby::buildModsMap() const -{ - QMap result; - QObject * mainWindow = qApp->activeWindow(); - if(!mainWindow) - mainWindow = parent(); - if(!mainWindow) - return result; //probably something is really wrong here - - while(mainWindow->parent()) - mainWindow = mainWindow->parent(); - const auto & modlist = qobject_cast(mainWindow)->getModList(); - - for(auto & modname : modlist.getModList()) - { - auto mod = modlist.getMod(modname); - if(mod.isEnabled()) - { - result[modname] = mod.getValue("version").toString(); - } - } - return result; -} - -bool Lobby::isModAvailable(const QString & modName, const QString & modVersion) const -{ - QObject * mainWindow = qApp->activeWindow(); - while(mainWindow->parent()) - mainWindow = mainWindow->parent(); - const auto & modlist = qobject_cast(mainWindow)->getModList(); - - if(!modlist.hasMod(modName)) - return false; - - auto mod = modlist.getMod(modName); - return (mod.isInstalled () || mod.isAvailable()) && (mod.getValue("version") == modVersion); -} - -void Lobby::serverCommand(const ServerCommand & command) try -{ - //initialize variables outside of switch block - const QString statusPlaceholder("%1 %2\n"); - const auto & args = command.arguments; - int amount; - int tagPoint; - QString joinStr; - switch(command.command) - { - case SRVERROR: - protocolAssert(args.size()); - ui->chatWidget->chatMessage("System error", args[0], true); - if(authentificationStatus == AuthStatus::AUTH_NONE) - authentificationStatus = AuthStatus::AUTH_ERROR; - break; - - case CREATED: - protocolAssert(args.size()); - hostSession = args[0]; - session = args[0]; - ui->chatWidget->setSession(session); - break; - - case SESSIONS: - protocolAssert(args.size()); - amount = args[0].toInt(); - protocolAssert(amount * 4 == (args.size() - 1)); - ui->sessionsTable->setRowCount(amount); - - tagPoint = 1; - for(int i = 0; i < amount; ++i) - { - QTableWidgetItem * sessionNameItem = new QTableWidgetItem(args[tagPoint++]); - ui->sessionsTable->setItem(i, 0, sessionNameItem); - - int playersJoined = args[tagPoint++].toInt(); - int playersTotal = args[tagPoint++].toInt(); - auto * sessionPlayerItem = new QTableWidgetItem(QString("%1/%2").arg(playersJoined).arg(playersTotal)); - ui->sessionsTable->setItem(i, 1, sessionPlayerItem); - - auto * sessionProtectedItem = new QTableWidgetItem(); - bool isPrivate = (args[tagPoint++] == "True"); - sessionProtectedItem->setData(Qt::UserRole, isPrivate); - if(isPrivate) - sessionProtectedItem->setIcon(QIcon("icons:room-private.png")); - ui->sessionsTable->setItem(i, 2, sessionProtectedItem); - } - break; - - case JOINED: - case KICKED: - protocolAssert(args.size() == 2); - if(args[1] == username) - { - hostModsMap.clear(); - session = ""; - ui->chatWidget->setSession(session); - ui->buttonReady->setText("Ready"); - ui->optNewGame->setChecked(true); - session = args[0]; - ui->chatWidget->setSession(session); - bool isHost = command.command == JOINED && hostSession == session; - ui->optNewGame->setEnabled(isHost); - ui->optLoadGame->setEnabled(isHost); - ui->stackedWidget->setCurrentWidget(command.command == JOINED ? ui->roomPage : ui->sessionsPage); - } - else - { - joinStr = (command.command == JOINED ? "%1 joined to the session %2" : "%1 left session %2"); - ui->chatWidget->sysMessage(joinStr.arg(args[1], args[0])); - } - break; - - case MODS: { - protocolAssert(args.size() > 0); - amount = args[0].toInt(); - protocolAssert(amount * 2 == (args.size() - 1)); - - tagPoint = 1; - for(int i = 0; i < amount; ++i, tagPoint += 2) - hostModsMap[args[tagPoint]] = args[tagPoint + 1]; - - updateMods(); - break; - } - - case CLIENTMODS: { - protocolAssert(args.size() >= 1); - auto & clientModsMap = clientsModsMap[args[0]]; - amount = args[1].toInt(); - protocolAssert(amount * 2 == (args.size() - 2)); - - tagPoint = 2; - for(int i = 0; i < amount; ++i, tagPoint += 2) - clientModsMap[args[tagPoint]] = args[tagPoint + 1]; - - break; - } - - - case STATUS: - protocolAssert(args.size() > 0); - amount = args[0].toInt(); - protocolAssert(amount * 2 == (args.size() - 1)); - - tagPoint = 1; - ui->playersList->clear(); - for(int i = 0; i < amount; ++i, tagPoint += 2) - { - if(args[tagPoint + 1] == "True") - ui->playersList->addItem(new QListWidgetItem(QIcon("icons:mod-enabled.png"), args[tagPoint])); - else - ui->playersList->addItem(new QListWidgetItem(QIcon("icons:mod-disabled.png"), args[tagPoint])); - - if(args[tagPoint] == username) - { - if(args[tagPoint + 1] == "True") - ui->buttonReady->setText("Not ready"); - else - ui->buttonReady->setText("Ready"); - } - } - break; - - case START: { - protocolAssert(args.size() == 1); - //actually start game - gameArgs << "--lobby"; - gameArgs << "--lobby-address" << serverUrl; - gameArgs << "--lobby-port" << QString::number(serverPort); - gameArgs << "--lobby-username" << username; - gameArgs << "--lobby-gamemode" << QString::number(isLoadGameMode); - gameArgs << "--uuid" << args[0]; - startGame(gameArgs); - break; - } - - case HOST: { - protocolAssert(args.size() == 2); - gameArgs << "--lobby-host"; - gameArgs << "--lobby-uuid" << args[0]; - gameArgs << "--lobby-connections" << args[1]; - break; - } - - case CHAT: { - protocolAssert(args.size() > 1); - QString msg; - for(int i = 1; i < args.size(); ++i) - msg += args[i]; - ui->chatWidget->chatMessage(args[0], msg); - break; - } - - case CHATCHANNEL: { - protocolAssert(args.size() > 2); - QString msg; - for(int i = 2; i < args.size(); ++i) - msg += args[i]; - ui->chatWidget->chatMessage(args[0], args[1], msg); - break; - } - - case CHANNEL: { - protocolAssert(args.size() == 1); - ui->chatWidget->setChannel(args[0]); - break; - } - - case HEALTH: { - socketLobby.send(ProtocolStrings[ALIVE]); - break; - } - - case USERS: { - protocolAssert(args.size() > 0); - amount = args[0].toInt(); - - protocolAssert(amount == (args.size() - 1)); - ui->chatWidget->clearUsers(); - for(int i = 0; i < amount; ++i) - { - ui->chatWidget->addUser(args[i + 1]); - } - break; - } - - case GAMEMODE: { - protocolAssert(args.size() == 1); - isLoadGameMode = args[0].toInt(); - if(isLoadGameMode) - ui->optLoadGame->setChecked(true); - else - ui->optNewGame->setChecked(true); - break; - } - - default: - ui->chatWidget->sysMessage("Unknown server command"); - } - - if(authentificationStatus == AuthStatus::AUTH_ERROR) - { - socketLobby.disconnectServer(); - } - else - { - authentificationStatus = AuthStatus::AUTH_OK; - ui->newButton->setEnabled(true); - } -} -catch(const ProtocolError & e) -{ - ui->chatWidget->chatMessage("System error", e.what(), true); -} - -void Lobby::dispatchMessage(QString txt) try -{ - if(txt.isEmpty()) - return; - - QStringList parseTags = txt.split(":>>"); - protocolAssert(parseTags.size() > 1 && parseTags[0].isEmpty() && !parseTags[1].isEmpty()); - - for(int c = 1; c < parseTags.size(); ++c) - { - QStringList parseArgs = parseTags[c].split(":"); - protocolAssert(parseArgs.size() > 1); - - auto ctype = ProtocolStrings.key(parseArgs[0]); - parseArgs.pop_front(); - ServerCommand cmd(ctype, parseArgs); - serverCommand(cmd); - } -} -catch(const ProtocolError & e) -{ - ui->chatWidget->chatMessage("System error", e.what(), true); -} - -void Lobby::onDisconnected() -{ - authentificationStatus = AuthStatus::AUTH_NONE; - session = ""; - ui->chatWidget->setSession(session); - ui->chatWidget->setChannel("global"); - ui->stackedWidget->setCurrentWidget(ui->sessionsPage); - ui->connectButton->setChecked(false); - ui->serverEdit->setEnabled(true); - ui->userEdit->setEnabled(true); - ui->newButton->setEnabled(false); - ui->joinButton->setEnabled(false); - ui->sessionsTable->setRowCount(0); -} - -void Lobby::protocolAssert(bool expr) -{ - if(!expr) - throw ProtocolError("Protocol error"); -} - -void Lobby::on_connectButton_toggled(bool checked) -{ - if(checked) - { - ui->connectButton->setText(tr("Disconnect")); - authentificationStatus = AuthStatus::AUTH_NONE; - username = ui->userEdit->text(); - ui->chatWidget->setUsername(username); - const int connectionTimeout = settings["launcher"]["connectionTimeout"].Integer(); - - auto serverStrings = ui->serverEdit->text().split(":"); - if(serverStrings.size() != 2) - { - QMessageBox::critical(this, "Connection error", "Server address must have the format URL:port"); - return; - } - - serverUrl = serverStrings[0]; - serverPort = serverStrings[1].toInt(); - - Settings node = settings.write["launcher"]; - node["lobbyUrl"].String() = serverUrl.toStdString(); - node["lobbyPort"].Integer() = serverPort; - node["lobbyUsername"].String() = username.toStdString(); - - ui->serverEdit->setEnabled(false); - ui->userEdit->setEnabled(false); - - ui->chatWidget->sysMessage("Connecting to " + serverUrl + ":" + QString::number(serverPort)); - //show text immediately - ui->chatWidget->repaint(); - qApp->processEvents(); - - socketLobby.connectServer(serverUrl, serverPort, username, connectionTimeout); - } - else - { - ui->connectButton->setText(tr("Connect")); - ui->serverEdit->setEnabled(true); - ui->userEdit->setEnabled(true); - ui->chatWidget->clearUsers(); - hostModsMap.clear(); - updateMods(); - socketLobby.disconnectServer(); - } -} - -void Lobby::updateMods() -{ - ui->modsList->clear(); - if(hostModsMap.empty()) - return; - - auto createModListWidget = [](const QIcon & icon, const QString & label, const QString & name, bool enableFlag, bool resolveFlag) - { - auto * lw = new QListWidgetItem(icon, label); - lw->setData(ModResolutionRoles::ModNameRole, name); - lw->setData(ModResolutionRoles::ModEnableRole, enableFlag); - lw->setData(ModResolutionRoles::ModResolvableRole, resolveFlag); - return lw; - }; - - auto enabledMods = buildModsMap(); - for(const auto & mod : hostModsMap.keys()) - { - auto & modValue = hostModsMap[mod]; - auto modName = QString("%1 (v%2)").arg(mod, modValue); - if(enabledMods.contains(mod)) - { - if(enabledMods[mod] == modValue) - enabledMods.remove(mod); //mod fully matches, remove from list - else - { - //mod version mismatch - ui->modsList->addItem(createModListWidget(QIcon("icons:mod-update.png"), modName, mod, true, false)); - } - } - else if(isModAvailable(mod, modValue)) - { - //mod is available and needs to be enabled - ui->modsList->addItem(createModListWidget(QIcon("icons:mod-enabled.png"), modName, mod, true, true)); - } - else - { - //mod is not available and needs to be installed - ui->modsList->addItem(createModListWidget(QIcon("icons:mod-delete.png"), modName, mod, true, false)); - } - } - for(const auto & remainMod : enabledMods.keys()) - { - auto modName = QString("%1 (v%2)").arg(remainMod, enabledMods[remainMod]); - //mod needs to be disabled - ui->modsList->addItem(createModListWidget(QIcon("icons:mod-disabled.png"), modName, remainMod, false, true)); - } - if(!ui->modsList->count()) - { - ui->buttonResolve->setEnabled(false); - ui->modsList->addItem(tr("No issues detected")); - } - else - { - ui->buttonResolve->setEnabled(true); - } -} - -void Lobby::on_newButton_clicked() -{ - new LobbyRoomRequest(socketLobby, "", buildModsMap(), this); -} - -void Lobby::on_joinButton_clicked() -{ - auto * item = ui->sessionsTable->item(ui->sessionsTable->currentRow(), 0); - if(item) - { - auto isPrivate = ui->sessionsTable->item(ui->sessionsTable->currentRow(), 2)->data(Qt::UserRole).toBool(); - if(isPrivate) - new LobbyRoomRequest(socketLobby, item->text(), buildModsMap(), this); - else - socketLobby.requestJoinSession(item->text(), "", buildModsMap()); - } -} - -void Lobby::on_buttonLeave_clicked() -{ - socketLobby.requestLeaveSession(session); -} - -void Lobby::on_buttonReady_clicked() -{ - if(ui->buttonReady->text() == "Ready") - ui->buttonReady->setText("Not ready"); - else - ui->buttonReady->setText("Ready"); - socketLobby.requestReadySession(session); -} - -void Lobby::on_sessionsTable_itemSelectionChanged() -{ - auto selection = ui->sessionsTable->selectedItems(); - ui->joinButton->setEnabled(!selection.empty()); -} - -void Lobby::on_playersList_currentRowChanged(int currentRow) -{ - ui->kickButton->setVisible(ui->playersList->currentItem() - && currentRow > 0 - && ui->playersList->currentItem()->text() != username); -} - -void Lobby::on_kickButton_clicked() -{ - if(ui->playersList->currentItem() && ui->playersList->currentItem()->text() != username) - socketLobby.send(ProtocolStrings[KICK].arg(ui->playersList->currentItem()->text())); -} - - -void Lobby::on_buttonResolve_clicked() -{ - QStringList toEnableList; - QStringList toDisableList; - auto items = ui->modsList->selectedItems(); - if(items.empty()) - { - for(int i = 0; i < ui->modsList->count(); ++i) - items.push_back(ui->modsList->item(i)); - } - - for(auto * item : items) - { - auto modName = item->data(ModResolutionRoles::ModNameRole); - if(modName.isNull()) - continue; - - bool modToEnable = item->data(ModResolutionRoles::ModEnableRole).toBool(); - bool modToResolve = item->data(ModResolutionRoles::ModResolvableRole).toBool(); - - if(!modToResolve) - continue; - - if(modToEnable) - toEnableList << modName.toString(); - else - toDisableList << modName.toString(); - } - - //disabling first, then enabling - for(auto & mod : toDisableList) - emit disableMod(mod); - for(auto & mod : toEnableList) - emit enableMod(mod); -} - -void Lobby::on_optNewGame_toggled(bool checked) -{ - if(checked) - { - if(isLoadGameMode) - socketLobby.send(ProtocolStrings[HOSTMODE].arg(GameMode::NEW_GAME)); - } -} - -void Lobby::on_optLoadGame_toggled(bool checked) -{ - if(checked) - { - if(!isLoadGameMode) - socketLobby.send(ProtocolStrings[HOSTMODE].arg(GameMode::LOAD_GAME)); - } -} - -void Lobby::onMessageSent(QString message) -{ - socketLobby.send(ProtocolStrings[MESSAGE].arg(message)); -} - -void Lobby::onChannelSwitch(QString channel) -{ - socketLobby.send(ProtocolStrings[SETCHANNEL].arg(channel)); -} diff --git a/launcher/lobby/lobby_moc.h b/launcher/lobby/lobby_moc.h deleted file mode 100644 index 23a8ddac1..000000000 --- a/launcher/lobby/lobby_moc.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * lobby_moc.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once -#include -#include "lobby.h" - -namespace Ui { -class Lobby; -} - -class Lobby : public QWidget -{ - Q_OBJECT - - void changeEvent(QEvent *event) override; -public: - explicit Lobby(QWidget *parent = nullptr); - ~Lobby(); - -signals: - - void enableMod(QString mod); - void disableMod(QString mod); - -public slots: - void updateMods(); - -private slots: - void dispatchMessage(QString); - void serverCommand(const ServerCommand &); - void onMessageSent(QString message); - void onChannelSwitch(QString channel); - - void on_connectButton_toggled(bool checked); - - void on_newButton_clicked(); - - void on_joinButton_clicked(); - - void on_buttonLeave_clicked(); - - void on_buttonReady_clicked(); - - void onDisconnected(); - - void on_sessionsTable_itemSelectionChanged(); - - void on_playersList_currentRowChanged(int currentRow); - - void on_kickButton_clicked(); - - void on_buttonResolve_clicked(); - - void on_optNewGame_toggled(bool checked); - - void on_optLoadGame_toggled(bool checked); - -private: - QString serverUrl; - int serverPort; - bool isLoadGameMode = false; - - Ui::Lobby *ui; - SocketLobby socketLobby; - QString hostSession; - QString session; - QString username; - QStringList gameArgs; - QMap hostModsMap; - QMap> clientsModsMap; - - enum AuthStatus - { - AUTH_NONE, AUTH_OK, AUTH_ERROR - }; - - AuthStatus authentificationStatus = AUTH_NONE; - -private: - QMap buildModsMap() const; - bool isModAvailable(const QString & modName, const QString & modVersion) const; - - - void protocolAssert(bool); -}; diff --git a/launcher/lobby/lobby_moc.ui b/launcher/lobby/lobby_moc.ui deleted file mode 100644 index 07406b877..000000000 --- a/launcher/lobby/lobby_moc.ui +++ /dev/null @@ -1,311 +0,0 @@ - - - Lobby - - - - 0 - 0 - 652 - 383 - - - - - - - - 0 - - - - - - 0 - 0 - - - - Username - - - - - - - - 0 - 0 - - - - Connect - - - true - - - - - - - 127.0.0.1:5002 - - - - - - - Server - - - - - - - - - - -1 - - - 0 - - - - - 0 - - - 10 - - - 0 - - - - - - 0 - 0 - - - - - - - - - - - 0 - 0 - - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - false - - - New room - - - - - - - false - - - Join room - - - - - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - false - - - 80 - - - false - - - true - - - 20 - - - 20 - - - - Session - - - - - Players - - - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - -1 - - - - - Kick player - - - - - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - - - - - Players in the room - - - - - - - Leave - - - - - - - Mods mismatch - - - - - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::MultiSelection - - - - - - - Ready - - - - - - - Resolve - - - - - - - 0 - - - - - New game - - - - - - - Load game - - - - - - - - - - - - - - - - Chat - QWidget -
lobby/chat_moc.h
- 1 -
-
- - -
diff --git a/launcher/lobby/lobbyroomrequest_moc.cpp b/launcher/lobby/lobbyroomrequest_moc.cpp deleted file mode 100644 index 62c2496db..000000000 --- a/launcher/lobby/lobbyroomrequest_moc.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * lobbyroomrequest_moc.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "lobbyroomrequest_moc.h" -#include "ui_lobbyroomrequest_moc.h" - -LobbyRoomRequest::LobbyRoomRequest(SocketLobby & socket, const QString & room, const QMap & mods, QWidget *parent) : - QDialog(parent), - ui(new Ui::LobbyRoomRequest), - socketLobby(socket), - mods(mods) -{ - ui->setupUi(this); - ui->nameEdit->setText(room); - if(!room.isEmpty()) - { - ui->nameEdit->setReadOnly(true); - ui->totalPlayers->setEnabled(false); - } - - show(); -} - -void LobbyRoomRequest::changeEvent(QEvent *event) -{ - if(event->type() == QEvent::LanguageChange) - { - ui->retranslateUi(this); - } - QDialog::changeEvent(event); -} - -LobbyRoomRequest::~LobbyRoomRequest() -{ - delete ui; -} - -void LobbyRoomRequest::on_buttonBox_accepted() -{ - if(ui->nameEdit->isReadOnly()) - { - socketLobby.requestJoinSession(ui->nameEdit->text(), ui->passwordEdit->text(), mods); - } - else - { - if(!ui->nameEdit->text().isEmpty()) - { - int totalPlayers = ui->totalPlayers->currentIndex() + 2; //where 2 is a minimum amount of players - socketLobby.requestNewSession(ui->nameEdit->text(), totalPlayers, ui->passwordEdit->text(), mods); - } - } -} - diff --git a/launcher/lobby/lobbyroomrequest_moc.h b/launcher/lobby/lobbyroomrequest_moc.h deleted file mode 100644 index eff7161e4..000000000 --- a/launcher/lobby/lobbyroomrequest_moc.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * lobbyroomrequest_moc.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#ifndef LOBBYROOMREQUEST_MOC_H -#define LOBBYROOMREQUEST_MOC_H - -#include -#include "lobby.h" - -namespace Ui { -class LobbyRoomRequest; -} - -class LobbyRoomRequest : public QDialog -{ - Q_OBJECT - - void changeEvent(QEvent *event) override; -public: - explicit LobbyRoomRequest(SocketLobby & socket, const QString & room, const QMap & mods, QWidget *parent = nullptr); - ~LobbyRoomRequest(); - -private slots: - void on_buttonBox_accepted(); - -private: - Ui::LobbyRoomRequest *ui; - SocketLobby & socketLobby; - QMap mods; -}; - -#endif // LOBBYROOMREQUEST_MOC_H diff --git a/launcher/lobby/lobbyroomrequest_moc.ui b/launcher/lobby/lobbyroomrequest_moc.ui deleted file mode 100644 index 5b8db9b7b..000000000 --- a/launcher/lobby/lobbyroomrequest_moc.ui +++ /dev/null @@ -1,151 +0,0 @@ - - - LobbyRoomRequest - - - Qt::WindowModal - - - - 0 - 0 - 227 - 188 - - - - Room settings - - - - - - false - - - true - - - - - - Room name - - - - - - - - - - Maximum players - - - - - - - - 0 - 0 - - - - 2 - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - - - - Password (optional) - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - rejected() - LobbyRoomRequest - reject() - - - 316 - 260 - - - 286 - 274 - - - - - buttonBox - accepted() - LobbyRoomRequest - accept() - - - 248 - 254 - - - 157 - 274 - - - - - diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index e9ae6d957..882925e34 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -53,7 +53,6 @@ void MainWindow::computeSidePanelSizes() QVector widgets = { ui->modslistButton, ui->settingsButton, - ui->lobbyButton, ui->aboutButton, ui->startEditorButton, ui->startGameButton @@ -86,10 +85,6 @@ MainWindow::MainWindow(QWidget * parent) ui->setupUi(this); - connect(ui->lobbyView, &Lobby::enableMod, ui->modlistView, &CModListView::enableModByName); - connect(ui->lobbyView, &Lobby::disableMod, ui->modlistView, &CModListView::disableModByName); - connect(ui->modlistView, &CModListView::modsChanged, ui->lobbyView, &Lobby::updateMods); - //load window settings QSettings s(Ui::teamName, Ui::appName); @@ -151,7 +146,6 @@ void MainWindow::enterSetup() { ui->startGameButton->setEnabled(false); ui->startEditorButton->setEnabled(false); - ui->lobbyButton->setEnabled(false); ui->settingsButton->setEnabled(false); ui->aboutButton->setEnabled(false); ui->modslistButton->setEnabled(false); @@ -165,7 +159,6 @@ void MainWindow::exitSetup() ui->startGameButton->setEnabled(true); ui->startEditorButton->setEnabled(true); - ui->lobbyButton->setEnabled(true); ui->settingsButton->setEnabled(true); ui->aboutButton->setEnabled(true); ui->modslistButton->setEnabled(true); @@ -228,12 +221,6 @@ void MainWindow::on_settingsButton_clicked() ui->tabListWidget->setCurrentIndex(TabRows::SETTINGS); } -void MainWindow::on_lobbyButton_clicked() -{ - ui->startGameButton->setEnabled(false); - ui->tabListWidget->setCurrentIndex(TabRows::LOBBY); -} - void MainWindow::on_aboutButton_clicked() { ui->startGameButton->setEnabled(true); diff --git a/launcher/mainwindow_moc.h b/launcher/mainwindow_moc.h index e9ff8d075..14d26c712 100644 --- a/launcher/mainwindow_moc.h +++ b/launcher/mainwindow_moc.h @@ -38,9 +38,8 @@ private: { MODS = 0, SETTINGS = 1, - LOBBY = 2, - SETUP = 3, - ABOUT = 4, + SETUP = 2, + ABOUT = 3, }; void changeEvent(QEvent *event) override; @@ -65,7 +64,6 @@ public slots: private slots: void on_modslistButton_clicked(); void on_settingsButton_clicked(); - void on_lobbyButton_clicked(); void on_startEditorButton_clicked(); void on_aboutButton_clicked(); }; diff --git a/launcher/mainwindow_moc.ui b/launcher/mainwindow_moc.ui index 1208bbe04..2f7a56934 100644 --- a/launcher/mainwindow_moc.ui +++ b/launcher/mainwindow_moc.ui @@ -133,56 +133,6 @@ - - - - - 1 - 10 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Lobby - - - - icons:menu-lobby.pngicons:menu-lobby.png - - - - 64 - 64 - - - - true - - - false - - - true - - - Qt::ToolButtonTextUnderIcon - - - true - - - @@ -370,7 +320,6 @@ - @@ -392,12 +341,6 @@
settingsView/csettingsview_moc.h
1 - - Lobby - QWidget -
lobby/lobby_moc.h
- 1 -
FirstLaunchView QWidget diff --git a/launcher/translation/chinese.ts b/launcher/translation/chinese.ts index 3eec1439d..5589be173 100644 --- a/launcher/translation/chinese.ts +++ b/launcher/translation/chinese.ts @@ -687,59 +687,31 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use 显示开场动画 - + Active 激活 - + Disabled 禁用 - + Enable 启用 - + Not Installed 未安装 - + Install 安装 - - Chat - - - Form - 窗体 - - - - Users in lobby - 大厅内用户 - - - - Global chat - 全局聊天 - - - - type you message - 输入你的消息 - - - - send - 发送 - - FirstLaunchView @@ -1055,119 +1027,7 @@ Heroes® of Might and Magic® III HD is currently not supported! - Lobby - - - - Connect - 连接 - - - - Username - 用户名 - - - - Server - 服务器 - - - - Session - 会话 - - - - Players - 玩家 - - - - Resolve - 解析 - - - - New game - 新游戏 - - - - Load game - 加载游戏 - - - - New room - 新房间 - - - - Join room - 加入房间 - - - - Ready - 准备 - - - - Mods mismatch - Mod统一翻译为模组 模组不匹配 - - - - Leave - 离开 - - - - Kick player - 踢出玩家 - - - - Players in the room - 房间内的玩家 - - - - Disconnect - 断开 - - - - No issues detected - 没有发现问题 - - - - LobbyRoomRequest - - - Room settings - 房间设置 - - - - Room name - 房间名称 - - - - Maximum players - 最大玩家数 - - - - Password (optional) - 密码(可选) - - - MainWindow @@ -1180,25 +1040,20 @@ Heroes® of Might and Magic® III HD is currently not supported! 设置 - + Help 帮助 - + Map Editor 地图编辑器 - + Start game 开始游戏 - - - Lobby - 大厅 - Mods diff --git a/launcher/translation/czech.ts b/launcher/translation/czech.ts index d0fb7a62f..38b38a4e9 100644 --- a/launcher/translation/czech.ts +++ b/launcher/translation/czech.ts @@ -681,59 +681,31 @@ Exkluzivní celá obrazovka - hra zakryje vaši celou obrazovku a použije vybra Zobrazit intro - + Active Aktivní - + Disabled - + Enable Povolit - + Not Installed - + Install Instalovat - - Chat - - - Form - Formulář - - - - Users in lobby - Uživatelé v předsíni - - - - Global chat - Obecná konverzace - - - - type you message - zadejte vaši zprávu - - - - send - poslat - - FirstLaunchView @@ -1047,118 +1019,6 @@ Heroes® of Might and Magic® III HD není v současnosti podporovaný!Automaticky (%1) - - Lobby - - - - Connect - Připojit - - - - Username - Uživatelské jméno - - - - Server - Server - - - - Session - Relace - - - - Players - Hráči - - - - Resolve - Vyřešit - - - - New game - Nová hra - - - - Load game - Načíst hru - - - - New room - Nová místnost - - - - Join room - Připojit se do místnosti - - - - Ready - Připraven - - - - Mods mismatch - Nesoulad modifikací - - - - Leave - Odejít - - - - Kick player - Vyhodit hráče - - - - Players in the room - Hráči v místnosti - - - - Disconnect - Odpojit - - - - No issues detected - Bez problémů - - - - LobbyRoomRequest - - - Room settings - Nastavení místnosti - - - - Room name - Název místnosti - - - - Maximum players - Maximum hráčů - - - - Password (optional) - Heslo (volitelné) - - MainWindow @@ -1172,25 +1032,20 @@ Heroes® of Might and Magic® III HD není v současnosti podporovaný!Nastavení - + Help Nápověda - + Map Editor Editor map - + Start game Spustit hru - - - Lobby - Předsíň - Mods diff --git a/launcher/translation/english.ts b/launcher/translation/english.ts index 5ed7e0e24..593169a2a 100644 --- a/launcher/translation/english.ts +++ b/launcher/translation/english.ts @@ -668,59 +668,31 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - + Active - + Disabled - + Enable - + Not Installed - + Install - - Chat - - - Form - - - - - Users in lobby - - - - - Global chat - - - - - type you message - - - - - send - - - FirstLaunchView @@ -1028,118 +1000,6 @@ Heroes® of Might and Magic® III HD is currently not supported! - - Lobby - - - - Connect - - - - - Username - - - - - Server - - - - - Session - - - - - Players - - - - - Resolve - - - - - New game - - - - - Load game - - - - - New room - - - - - Join room - - - - - Ready - - - - - Mods mismatch - - - - - Leave - - - - - Kick player - - - - - Players in the room - - - - - Disconnect - - - - - No issues detected - - - - - LobbyRoomRequest - - - Room settings - - - - - Room name - - - - - Maximum players - - - - - Password (optional) - - - MainWindow @@ -1153,25 +1013,20 @@ Heroes® of Might and Magic® III HD is currently not supported! - + Help - + Map Editor - + Start game - - - Lobby - - Mods diff --git a/launcher/translation/french.ts b/launcher/translation/french.ts index b812bee3d..d73d50dd2 100644 --- a/launcher/translation/french.ts +++ b/launcher/translation/french.ts @@ -679,59 +679,31 @@ Mode exclusif plein écran - le jeu couvrira l"intégralité de votre écra Montrer l'intro - + Active Actif - + Disabled Désactivé - + Enable Activé - + Not Installed Pas Installé - + Install Installer - - Chat - - - Form - - - - - Users in lobby - - - - - Global chat - - - - - type you message - - - - - send - - - FirstLaunchView @@ -1045,118 +1017,6 @@ Heroes® of Might and Magic® III HD n"est actuellement pas pris en charge Auto (%1) - - Lobby - - - Username - Nom d'utilisateur - - - - - Connect - Connecter - - - - Server - Serveur - - - - New room - Nouveau salon - - - - Join room - Rejoindre le salon - - - - Session - Session - - - - Players - Joueurs - - - - Kick player - Jeter le joueur - - - - Players in the room - Joueurs dans le salon - - - - Leave - Quitter - - - - Mods mismatch - Incohérence de mods - - - - Ready - Prêt - - - - Resolve - Résoudre - - - - New game - Nouvelle partie - - - - Load game - Charger une partie - - - - Disconnect - Déconnecter - - - - No issues detected - Pas de problème détecté - - - - LobbyRoomRequest - - - Room settings - Paramètres de salon - - - - Room name - Nom de salon - - - - Maximum players - Maximum de joueurs - - - - Password (optional) - Mot de passe (optionnel) - - MainWindow @@ -1176,21 +1036,16 @@ Heroes® of Might and Magic® III HD n"est actuellement pas pris en charge - Lobby - Salle d'attente - - - Help Aide - + Map Editor Éditeur de carte - + Start game Démarrer une partie diff --git a/launcher/translation/german.ts b/launcher/translation/german.ts index 65a6f03c9..36eab6711 100644 --- a/launcher/translation/german.ts +++ b/launcher/translation/german.ts @@ -681,59 +681,31 @@ Exklusiver Vollbildmodus - das Spiel bedeckt den gesamten Bildschirm und verwend Intro anzeigen - + Active Aktiv - + Disabled Deaktiviert - + Enable Aktivieren - + Not Installed Nicht installiert - + Install Installieren - - Chat - - - Form - Formular - - - - Users in lobby - Benutzer in der Lobby - - - - Global chat - Globaler Chat - - - - type you message - Nachricht eingeben - - - - send - senden - - FirstLaunchView @@ -1047,118 +1019,6 @@ Heroes III: HD Edition wird derzeit nicht unterstützt! Auto (%1) - - Lobby - - - - Connect - Verbinden - - - - Username - Benutzername - - - - Server - Server - - - - Session - Sitzung - - - - Players - Spieler - - - - Resolve - Auflösen - - - - New game - Neues Spiel - - - - Load game - Spiel laden - - - - New room - Neuer Raum - - - - Join room - Raum beitreten - - - - Ready - Bereit - - - - Mods mismatch - Mods stimmen nicht überein - - - - Leave - Verlassen - - - - Kick player - Spieler kicken - - - - Players in the room - Spieler im Raum - - - - Disconnect - Verbindung trennen - - - - No issues detected - Keine Probleme festgestellt - - - - LobbyRoomRequest - - - Room settings - Raumeinstellungen - - - - Room name - Raumname - - - - Maximum players - Maximale Spieler - - - - Password (optional) - Passwort (optional) - - MainWindow @@ -1178,21 +1038,16 @@ Heroes III: HD Edition wird derzeit nicht unterstützt! - Lobby - Lobby - - - Help Hilfe - + Map Editor Karteneditor - + Start game Spiel starten diff --git a/launcher/translation/polish.ts b/launcher/translation/polish.ts index 30f1a7a69..d4748758f 100644 --- a/launcher/translation/polish.ts +++ b/launcher/translation/polish.ts @@ -681,59 +681,31 @@ Pełny ekran klasyczny - gra przysłoni cały ekran uruchamiając się w wybrane Pokaż intro - + Active Aktywny - + Disabled Wyłączone - + Enable Włącz - + Not Installed Nie zainstalowano - + Install Zainstaluj - - Chat - - - Form - Okno - - - - Users in lobby - Gracze w lobby - - - - Global chat - Globalny czat - - - - type you message - napisz wiadomość - - - - send - wyślij - - FirstLaunchView @@ -1047,118 +1019,6 @@ Heroes III: HD Edition nie jest obecnie wspierane! - - Lobby - - - - Connect - Połącz - - - - Username - Nazwa użytkownika - - - - Server - Serwer - - - - Session - Sesja - - - - Players - Gracze - - - - Resolve - Rozwiąż - - - - New game - Nowa gra - - - - Load game - Wczytaj grę - - - - New room - Nowy pokój - - - - Join room - Dołącz - - - - Ready - Zgłoś gotowość - - - - Mods mismatch - Niezgodność modów - - - - Leave - Wyjdź - - - - Kick player - Wyrzuć gracza - - - - Players in the room - Gracze w pokoju - - - - Disconnect - Rozłącz - - - - No issues detected - Nie znaleziono problemów - - - - LobbyRoomRequest - - - Room settings - Ustawienia pokoju - - - - Room name - Nazwa pokoju - - - - Maximum players - Maks. ilość graczy - - - - Password (optional) - Hasło (opcjonalnie) - - MainWindow @@ -1172,25 +1032,20 @@ Heroes III: HD Edition nie jest obecnie wspierane! Ustawienia - + Help Pomoc - + Map Editor Edytor map - + Start game Uruchom grę - - - Lobby - Lobby - Mods diff --git a/launcher/translation/russian.ts b/launcher/translation/russian.ts index 498a014d3..2ee2ae217 100644 --- a/launcher/translation/russian.ts +++ b/launcher/translation/russian.ts @@ -668,59 +668,31 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use Вступление - + Active Активен - + Disabled Отключен - + Enable Включить - + Not Installed Не установлен - + Install Установить - - Chat - - - Form - - - - - Users in lobby - - - - - Global chat - - - - - type you message - - - - - send - - - FirstLaunchView @@ -1034,118 +1006,6 @@ Heroes® of Might and Magic® III HD is currently not supported! Авто (%1) - - Lobby - - - - Connect - Подключиться - - - - Username - Имя пользователя - - - - Server - Сервер - - - - Session - Сессия - - - - Players - Игроки - - - - Resolve - Скорректировать - - - - New game - Новая игра - - - - Load game - Загрузить игру - - - - New room - Создать комнату - - - - Join room - Присоединиться к комнате - - - - Ready - Готово - - - - Mods mismatch - Моды не совпадают - - - - Leave - Выйти - - - - Kick player - Выгнать игрока - - - - Players in the room - Игроки в комнате - - - - Disconnect - Отключиться - - - - No issues detected - Проблем не обнаружено - - - - LobbyRoomRequest - - - Room settings - Настройки комнаты - - - - Room name - Название - - - - Maximum players - Максимум игроков - - - - Password (optional) - Пароль (не обязательно) - - MainWindow @@ -1159,25 +1019,20 @@ Heroes® of Might and Magic® III HD is currently not supported! Параметры - + Help - + Map Editor Редактор карт - + Start game Играть - - - Lobby - Лобби - Mods diff --git a/launcher/translation/spanish.ts b/launcher/translation/spanish.ts index ec1903015..b3daffaf1 100644 --- a/launcher/translation/spanish.ts +++ b/launcher/translation/spanish.ts @@ -668,59 +668,31 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use Idioma de los datos de Heroes III. - + Active Activado - + Disabled Desactivado - + Enable Activar - + Not Installed No Instalado - + Install Instalar - - Chat - - - Form - - - - - Users in lobby - - - - - Global chat - - - - - type you message - - - - - send - - - FirstLaunchView @@ -1034,118 +1006,6 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi Automático (%1) - - Lobby - - - - Connect - Conectar - - - - Username - Nombre de usuario - - - - Server - Servidor - - - - Session - Sesión - - - - Players - Jugadores - - - - Resolve - Resolver - - - - New game - Nueva partida - - - - Load game - Cargar partida - - - - New room - Nueva sala - - - - Join room - Unirse a la sala - - - - Ready - Listo - - - - Mods mismatch - No coinciden los mods - - - - Leave - Salir - - - - Kick player - Expulsar jugador - - - - Players in the room - Jugadores en la sala - - - - Disconnect - Desconectar - - - - No issues detected - No se han detectado problemas - - - - LobbyRoomRequest - - - Room settings - Configuración de la sala - - - - Room name - Nombre de la sala - - - - Maximum players - Jugadores máximos - - - - Password (optional) - Contraseña (opcional) - - MainWindow @@ -1159,25 +1019,20 @@ Ten en cuenta que para usar VCMI debes ser dueño de los archivos de datos origi Configuración - + Help - + Map Editor Editor de Mapas - + Start game Iniciar juego - - - Lobby - Sala de Espera - Mods diff --git a/launcher/translation/ukrainian.ts b/launcher/translation/ukrainian.ts index 37afc3acd..5a8f80cd5 100644 --- a/launcher/translation/ukrainian.ts +++ b/launcher/translation/ukrainian.ts @@ -681,59 +681,31 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use Вступні відео - + Active Активні - + Disabled Деактивований - + Enable Активувати - + Not Installed Не встановлено - + Install Встановити - - Chat - - - Form - - - - - Users in lobby - Гравців у лобі - - - - Global chat - Загальний чат - - - - type you message - введіть повідомлення - - - - send - Відправити - - FirstLaunchView @@ -1047,118 +1019,6 @@ Heroes® of Might and Magic® III HD наразі не підтримуєтьс Авто (%1) - - Lobby - - - - Connect - Підключитися - - - - Username - Ім'я користувача - - - - Server - Сервер - - - - Session - Сесія - - - - Players - Гравці - - - - Resolve - Розв'язати - - - - New game - Нова гра - - - - Load game - Завантажити гру - - - - New room - Створити кімнату - - - - Join room - Приєднатися до кімнати - - - - Ready - Готовність - - - - Mods mismatch - Модифікації, що не збігаються - - - - Leave - Вийти з кімнати - - - - Kick player - Виключити гравця - - - - Players in the room - Гравці у кімнаті - - - - Disconnect - Від'єднатися - - - - No issues detected - Проблем не виявлено - - - - LobbyRoomRequest - - - Room settings - Налаштування кімнати - - - - Room name - Назва кімнати - - - - Maximum players - Максимум гравців - - - - Password (optional) - Пароль (за бажанням) - - MainWindow @@ -1178,21 +1038,16 @@ Heroes® of Might and Magic® III HD наразі не підтримуєтьс - Lobby - Лобі - - - Help Допомога - + Map Editor Редактор мап - + Start game Грати diff --git a/launcher/translation/vietnamese.ts b/launcher/translation/vietnamese.ts index 175c609e3..58affaf3b 100644 --- a/launcher/translation/vietnamese.ts +++ b/launcher/translation/vietnamese.ts @@ -674,59 +674,31 @@ Toàn màn hình riêng biệt - Trò chơi chạy toàn màn hình và dùng đ Hiện thị giới thiệu - + Active Bật - + Disabled Tắt - + Enable Bật - + Not Installed Chưa cài đặt - + Install Cài đặt - - Chat - - - Form - - - - - Users in lobby - - - - - Global chat - - - - - type you message - - - - - send - - - FirstLaunchView @@ -1040,118 +1012,6 @@ Hiện tại chưa hỗ trợ Heroes® of Might and Magic® III HD!Tự động (%1) - - Lobby - - - - Connect - Kết nối - - - - Username - Tên đăng nhập - - - - Server - Máy chủ - - - - Session - Phiên - - - - Players - Người chơi - - - - Resolve - Phân tích - - - - New game - Tạo mới - - - - Load game - Tải lại - - - - New room - Tạo phòng - - - - Join room - Vào phòng - - - - Ready - Sẵn sàng - - - - Mods mismatch - Bản sửa đổi chưa giống - - - - Leave - Rời khỏi - - - - Kick player - Mời ra - - - - Players in the room - Người chơi trong phòng - - - - Disconnect - Thoát - - - - No issues detected - Không có vấn đề - - - - LobbyRoomRequest - - - Room settings - Cài đặt phòng - - - - Room name - Tên phòng - - - - Maximum players - Số người chơi tối đa - - - - Password (optional) - Mật khẩu (tùy chọn) - - MainWindow @@ -1165,25 +1025,20 @@ Hiện tại chưa hỗ trợ Heroes® of Might and Magic® III HD!Cài đặt - + Help - + Map Editor Tạo bản đồ - + Start game Chơi ngay - - - Lobby - Sảnh - Mods From 476a05fed3d2e6f32f126c13f78017cc73e7303d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 11 Jan 2024 22:51:36 +0200 Subject: [PATCH 31/98] Formatting --- client/globalLobby/GlobalLobbyClient.cpp | 37 +++++---- client/globalLobby/GlobalLobbyClient.h | 1 + client/globalLobby/GlobalLobbyLoginWindow.cpp | 16 ++-- client/globalLobby/GlobalLobbyServerSetup.cpp | 16 ++-- client/globalLobby/GlobalLobbyWidget.h | 1 + client/globalLobby/GlobalLobbyWindow.cpp | 10 +-- client/globalLobby/GlobalLobbyWindow.h | 1 - lobby/LobbyDatabase.cpp | 31 +++---- lobby/LobbyDefines.h | 6 +- lobby/LobbyServer.cpp | 80 +++++++++---------- lobby/LobbyServer.h | 2 +- 11 files changed, 96 insertions(+), 105 deletions(-) diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index bcef12a03..a6af74c60 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -11,15 +11,15 @@ #include "StdInc.h" #include "GlobalLobbyClient.h" -#include "GlobalLobbyWindow.h" #include "GlobalLobbyLoginWindow.h" +#include "GlobalLobbyWindow.h" #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" #include "../windows/InfoWindows.h" -#include "../../lib/MetaString.h" #include "../../lib/CConfigHandler.h" +#include "../../lib/MetaString.h" #include "../../lib/TextOperations.h" #include "../../lib/network/NetworkClient.h" @@ -52,32 +52,32 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptr()) + if(!loginWindowPtr || !GH.windows().topWindow()) throw std::runtime_error("lobby connection finished without active login window!"); { @@ -98,7 +98,7 @@ void GlobalLobbyClient::receiveLoginFailed(const JsonNode & json) { auto loginWindowPtr = loginWindow.lock(); - if (!loginWindowPtr || !GH.windows().topWindow()) + if(!loginWindowPtr || !GH.windows().topWindow()) throw std::runtime_error("lobby connection finished without active login window!"); loginWindowPtr->onConnectionFailed(json["reason"].String()); @@ -116,7 +116,7 @@ void GlobalLobbyClient::receiveLoginSuccess(const JsonNode & json) auto loginWindowPtr = loginWindow.lock(); - if (!loginWindowPtr || !GH.windows().topWindow()) + if(!loginWindowPtr || !GH.windows().topWindow()) throw std::runtime_error("lobby connection finished without active login window!"); loginWindowPtr->onConnectionSuccess(); @@ -124,7 +124,7 @@ void GlobalLobbyClient::receiveLoginSuccess(const JsonNode & json) void GlobalLobbyClient::receiveChatHistory(const JsonNode & json) { - for (auto const & entry : json["messages"].Vector()) + for(const auto & entry : json["messages"].Vector()) { std::string accountID = entry["accountID"].String(); std::string displayName = entry["displayName"].String(); @@ -164,7 +164,7 @@ void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr()) + if(!loginWindowPtr || !GH.windows().topWindow()) throw std::runtime_error("lobby connection failed without active login window!"); logGlobal->warn("Connection to game lobby failed! Reason: %s", errorMessage); @@ -214,7 +214,7 @@ void GlobalLobbyClient::sendMessage(const JsonNode & data) std::string payloadString = data.toJson(true); // FIXME: find better approach - uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); + uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); uint8_t * payloadEnd = payloadBegin + payloadString.size(); std::vector payloadBuffer(payloadBegin, payloadEnd); @@ -237,7 +237,7 @@ bool GlobalLobbyClient::isConnected() std::shared_ptr GlobalLobbyClient::createLoginWindow() { auto loginWindowPtr = loginWindow.lock(); - if (loginWindowPtr) + if(loginWindowPtr) return loginWindowPtr; auto loginWindowNew = std::make_shared(); @@ -249,7 +249,7 @@ std::shared_ptr GlobalLobbyClient::createLoginWindow() std::shared_ptr GlobalLobbyClient::createLobbyWindow() { auto lobbyWindowPtr = lobbyWindow.lock(); - if (lobbyWindowPtr) + if(lobbyWindowPtr) return lobbyWindowPtr; lobbyWindowPtr = std::make_shared(); @@ -257,4 +257,3 @@ std::shared_ptr GlobalLobbyClient::createLobbyWindow() lobbyWindowLock = lobbyWindowPtr; return lobbyWindowPtr; } - diff --git a/client/globalLobby/GlobalLobbyClient.h b/client/globalLobby/GlobalLobbyClient.h index 461cf7e81..335c4dc3e 100644 --- a/client/globalLobby/GlobalLobbyClient.h +++ b/client/globalLobby/GlobalLobbyClient.h @@ -42,6 +42,7 @@ class GlobalLobbyClient : public INetworkClientListener, boost::noncopyable void receiveChatHistory(const JsonNode & json); void receiveChatMessage(const JsonNode & json); void receiveActiveAccounts(const JsonNode & json); + public: explicit GlobalLobbyClient(); ~GlobalLobbyClient(); diff --git a/client/globalLobby/GlobalLobbyLoginWindow.cpp b/client/globalLobby/GlobalLobbyLoginWindow.cpp index dce37c03a..adb8244b0 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.cpp +++ b/client/globalLobby/GlobalLobbyLoginWindow.cpp @@ -14,18 +14,18 @@ #include "GlobalLobbyClient.h" #include "GlobalLobbyWindow.h" -#include "../gui/CGuiHandler.h" -#include "../gui/WindowHandler.h" -#include "../widgets/TextControls.h" -#include "../widgets/Images.h" -#include "../widgets/Buttons.h" -#include "../widgets/MiscWidgets.h" #include "../CGameInfo.h" #include "../CServerHandler.h" +#include "../gui/CGuiHandler.h" +#include "../gui/WindowHandler.h" +#include "../widgets/Buttons.h" +#include "../widgets/Images.h" +#include "../widgets/MiscWidgets.h" +#include "../widgets/TextControls.h" +#include "../../lib/CConfigHandler.h" #include "../../lib/CGeneralTextHandler.h" #include "../../lib/MetaString.h" -#include "../../lib/CConfigHandler.h" GlobalLobbyLoginWindow::GlobalLobbyLoginWindow() : CWindowObject(BORDERED) @@ -66,7 +66,7 @@ void GlobalLobbyLoginWindow::onLogin() config->String() = inputUsername->getText(); labelStatus->setText(CGI->generaltexth->translate("vcmi.lobby.login.connecting")); - if (!CSH->getGlobalLobby().isConnected()) + if(!CSH->getGlobalLobby().isConnected()) CSH->getGlobalLobby().connect(); buttonClose->block(true); } diff --git a/client/globalLobby/GlobalLobbyServerSetup.cpp b/client/globalLobby/GlobalLobbyServerSetup.cpp index d2f0f2ffd..9a930932f 100644 --- a/client/globalLobby/GlobalLobbyServerSetup.cpp +++ b/client/globalLobby/GlobalLobbyServerSetup.cpp @@ -11,17 +11,17 @@ #include "StdInc.h" #include "GlobalLobbyServerSetup.h" -#include "../gui/CGuiHandler.h" -#include "../widgets/TextControls.h" -#include "../widgets/Images.h" -#include "../widgets/Buttons.h" #include "../CGameInfo.h" #include "../CServerHandler.h" +#include "../gui/CGuiHandler.h" #include "../mainmenu/CMainMenu.h" +#include "../widgets/Buttons.h" +#include "../widgets/Images.h" +#include "../widgets/TextControls.h" +#include "../../lib/CConfigHandler.h" #include "../../lib/CGeneralTextHandler.h" #include "../../lib/MetaString.h" -#include "../../lib/CConfigHandler.h" GlobalLobbyServerSetup::GlobalLobbyServerSetup() : CWindowObject(BORDERED) @@ -85,12 +85,12 @@ void GlobalLobbyServerSetup::updateDescription() { MetaString description; description.appendRawString("%s %s %s"); - if (toggleRoomType->getSelected() == 0) + if(toggleRoomType->getSelected() == 0) description.replaceTextID("vcmi.lobby.room.description.public"); else description.replaceTextID("vcmi.lobby.room.description.private"); - if (toggleGameMode->getSelected() == 0) + if(toggleGameMode->getSelected() == 0) description.replaceTextID("vcmi.lobby.room.description.new"); else description.replaceTextID("vcmi.lobby.room.description.load"); @@ -124,7 +124,7 @@ void GlobalLobbyServerSetup::onGameModeChanged(int value) void GlobalLobbyServerSetup::onCreate() { - if (toggleGameMode->getSelected() == 0) + if(toggleGameMode->getSelected() == 0) { CSH->resetStateForLobby(StartInfo::NEW_GAME, nullptr); CSH->screenType = ESelectionScreen::newGame; diff --git a/client/globalLobby/GlobalLobbyWidget.h b/client/globalLobby/GlobalLobbyWidget.h index c22c0deae..ed61dd314 100644 --- a/client/globalLobby/GlobalLobbyWidget.h +++ b/client/globalLobby/GlobalLobbyWidget.h @@ -16,6 +16,7 @@ class GlobalLobbyWindow; class GlobalLobbyWidget : public InterfaceObjectConfigurable { GlobalLobbyWindow * window; + public: GlobalLobbyWidget(GlobalLobbyWindow * window); diff --git a/client/globalLobby/GlobalLobbyWindow.cpp b/client/globalLobby/GlobalLobbyWindow.cpp index 9f91d0ccc..11ce619b1 100644 --- a/client/globalLobby/GlobalLobbyWindow.cpp +++ b/client/globalLobby/GlobalLobbyWindow.cpp @@ -11,20 +11,20 @@ #include "StdInc.h" #include "GlobalLobbyWindow.h" -#include "GlobalLobbyWidget.h" #include "GlobalLobbyClient.h" #include "GlobalLobbyServerSetup.h" +#include "GlobalLobbyWidget.h" +#include "../CServerHandler.h" #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" #include "../widgets/TextControls.h" -#include "../CServerHandler.h" -#include "../../lib/MetaString.h" #include "../../lib/CConfigHandler.h" +#include "../../lib/MetaString.h" -GlobalLobbyWindow::GlobalLobbyWindow(): - CWindowObject(BORDERED) +GlobalLobbyWindow::GlobalLobbyWindow() + : CWindowObject(BORDERED) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; widget = std::make_shared(this); diff --git a/client/globalLobby/GlobalLobbyWindow.h b/client/globalLobby/GlobalLobbyWindow.h index 5958ab495..d9d792f7e 100644 --- a/client/globalLobby/GlobalLobbyWindow.h +++ b/client/globalLobby/GlobalLobbyWindow.h @@ -26,5 +26,4 @@ public: void doCreateGameRoom(); void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when); - }; diff --git a/lobby/LobbyDatabase.cpp b/lobby/LobbyDatabase.cpp index 30c0a2e7a..30332b4e4 100644 --- a/lobby/LobbyDatabase.cpp +++ b/lobby/LobbyDatabase.cpp @@ -251,7 +251,7 @@ bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountID) bool result = false; isPlayerInAnyGameRoomStatement->setBinds(accountID); - if (isPlayerInAnyGameRoomStatement->execute()) + if(isPlayerInAnyGameRoomStatement->execute()) isPlayerInAnyGameRoomStatement->getColumns(result); isPlayerInAnyGameRoomStatement->reset(); @@ -263,7 +263,7 @@ bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountID, const std: bool result = false; isPlayerInGameRoomStatement->setBinds(accountID, roomID); - if (isPlayerInGameRoomStatement->execute()) + if(isPlayerInGameRoomStatement->execute()) isPlayerInGameRoomStatement->getColumns(result); isPlayerInGameRoomStatement->reset(); @@ -330,27 +330,18 @@ void LobbyDatabase::insertAccessCookie(const std::string & accountID, const std: insertAccessCookieStatement->executeOnce(accountID, accessCookieUUID); } -void LobbyDatabase::updateAccessCookie(const std::string & accountID, const std::string & accessCookieUUID) -{ +void LobbyDatabase::updateAccessCookie(const std::string & accountID, const std::string & accessCookieUUID) {} -} +void LobbyDatabase::updateAccountLoginTime(const std::string & accountID) {} -void LobbyDatabase::updateAccountLoginTime(const std::string & accountID) -{ - -} - -void LobbyDatabase::updateActiveAccount(const std::string & accountID, bool isActive) -{ - -} +void LobbyDatabase::updateActiveAccount(const std::string & accountID, bool isActive) {} std::string LobbyDatabase::getAccountDisplayName(const std::string & accountID) { std::string result; getAccountDisplayNameStatement->setBinds(accountID); - if (getAccountDisplayNameStatement->execute()) + if(getAccountDisplayNameStatement->execute()) getAccountDisplayNameStatement->getColumns(result); getAccountDisplayNameStatement->reset(); @@ -367,7 +358,7 @@ LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & acco bool result = false; isAccountCookieValidStatement->setBinds(accountID, accessCookieUUID, cookieLifetime.count()); - if (isAccountCookieValidStatement->execute()) + if(isAccountCookieValidStatement->execute()) isAccountCookieValidStatement->getColumns(result); isAccountCookieValidStatement->reset(); @@ -394,7 +385,7 @@ bool LobbyDatabase::isAccountNameExists(const std::string & displayName) bool result = false; isAccountNameExistsStatement->setBinds(displayName); - if (isAccountNameExistsStatement->execute()) + if(isAccountNameExistsStatement->execute()) isAccountNameExistsStatement->getColumns(result); isAccountNameExistsStatement->reset(); return result; @@ -405,7 +396,7 @@ bool LobbyDatabase::isAccountIDExists(const std::string & accountID) bool result = false; isAccountIDExistsStatement->setBinds(accountID); - if (isAccountIDExistsStatement->execute()) + if(isAccountIDExistsStatement->execute()) isAccountIDExistsStatement->getColumns(result); isAccountIDExistsStatement->reset(); return result; @@ -434,7 +425,7 @@ std::string LobbyDatabase::getIdleGameRoom(const std::string & hostAccountID) { std::string result; - if (getIdleGameRoomStatement->execute()) + if(getIdleGameRoomStatement->execute()) getIdleGameRoomStatement->getColumns(result); getIdleGameRoomStatement->reset(); @@ -445,7 +436,7 @@ std::string LobbyDatabase::getAccountGameRoom(const std::string & accountID) { std::string result; - if (getAccountGameRoomStatement->execute()) + if(getAccountGameRoomStatement->execute()) getAccountGameRoomStatement->getColumns(result); getAccountGameRoomStatement->reset(); diff --git a/lobby/LobbyDefines.h b/lobby/LobbyDefines.h index bcf02e187..45a851404 100644 --- a/lobby/LobbyDefines.h +++ b/lobby/LobbyDefines.h @@ -48,9 +48,9 @@ enum class LobbyInviteStatus : int32_t enum class LobbyRoomState : int32_t { - IDLE, // server is ready but no players are in the room - PUBLIC, // host has joined and allows anybody to join - PRIVATE, // host has joined but only allows those he invited to join + IDLE, // server is ready but no players are in the room + PUBLIC, // host has joined and allows anybody to join + PRIVATE, // host has joined but only allows those he invited to join //BUSY, // match is ongoing //CANCELLED, // game room was cancelled without starting the game //CLOSED, // game room was closed after playing for some time diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 9a1f21f6a..52c8f5e49 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -13,24 +13,24 @@ #include "LobbyDatabase.h" #include "../lib/JsonNode.h" -#include "../lib/network/NetworkServer.h" #include "../lib/network/NetworkConnection.h" +#include "../lib/network/NetworkServer.h" -#include #include +#include -static const auto accountCookieLifetime = std::chrono::hours(24*7); +static const auto accountCookieLifetime = std::chrono::hours(24 * 7); bool LobbyServer::isAccountNameValid(const std::string & accountName) { - if (accountName.size() < 4) + if(accountName.size() < 4) return false; - if (accountName.size() < 20) + if(accountName.size() < 20) return false; - for (auto const & c : accountName) - if (!std::isalnum(c)) + for(const auto & c : accountName) + if(!std::isalnum(c)) return false; return true; @@ -44,8 +44,8 @@ std::string LobbyServer::sanitizeChatMessage(const std::string & inputString) co NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID) { - for (auto const & account : activeAccounts) - if (account.second.accountID == accountID) + for(const auto & account : activeAccounts) + if(account.second.accountID == accountID) return account.first; return nullptr; @@ -53,8 +53,8 @@ NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID) NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) { - for (auto const & account : activeGameRooms) - if (account.second.roomID == gameRoomID) + for(const auto & account : activeGameRooms) + if(account.second.roomID == gameRoomID) return account.first; return nullptr; @@ -105,7 +105,7 @@ void LobbyServer::sendLoginSuccess(const NetworkConnectionPtr & target, const st JsonNode reply; reply["type"].String() = "loginSuccess"; reply["accountCookie"].String() = accountCookie; - if (!displayName.empty()) + if(!displayName.empty()) reply["displayName"].String() = displayName; sendMessage(target, reply); } @@ -142,7 +142,7 @@ void LobbyServer::broadcastActiveAccounts() JsonNode jsonEntry; jsonEntry["accountID"].String() = account.accountID; jsonEntry["displayName"].String() = account.displayName; -// jsonEntry["status"].String() = account.status; + // jsonEntry["status"].String() = account.status; reply["accounts"].Vector().push_back(jsonEntry); } @@ -224,10 +224,10 @@ void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection) void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, const std::vector & message) { // proxy connection - no processing, only redirect - if (activeProxies.count(connection)) + if(activeProxies.count(connection)) { auto lockedPtr = activeProxies.at(connection).lock(); - if (lockedPtr) + if(lockedPtr) lockedPtr->sendPacket(message); return; } @@ -238,7 +238,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons // TODO: validate json based on received message type // communication messages from vcmiclient - if (activeAccounts.count(connection)) + if(activeAccounts.count(connection)) { if(json["type"].String() == "sendChatMessage") return receiveSendChatMessage(connection, json); @@ -259,7 +259,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons } // communication messages from vcmiserver - if (activeGameRooms.count(connection)) + if(activeGameRooms.count(connection)) { if(json["type"].String() == "leaveGameRoom") return receiveLeaveGameRoom(connection, json); @@ -294,7 +294,7 @@ void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection std::string messageTextClean = sanitizeChatMessage(messageText); std::string displayName = database->getAccountDisplayName(accountID); - if (messageTextClean.empty()) + if(messageTextClean.empty()) return; database->insertChatMessage(accountID, "global", "english", messageText); @@ -308,10 +308,10 @@ void LobbyServer::receiveClientRegister(const NetworkConnectionPtr & connection, std::string displayName = json["displayName"].String(); std::string language = json["language"].String(); - if (isAccountNameValid(displayName)) + if(isAccountNameValid(displayName)) return sendLoginFailed(connection, "Illegal account name"); - if (database->isAccountNameExists(displayName)) + if(database->isAccountNameExists(displayName)) return sendLoginFailed(connection, "Account name already in use"); std::string accountCookie = boost::uuids::to_string(boost::uuids::random_generator()()); @@ -330,12 +330,12 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co std::string language = json["language"].String(); std::string version = json["version"].String(); - if (!database->isAccountIDExists(accountID)) + if(!database->isAccountIDExists(accountID)) return sendLoginFailed(connection, "Account not found"); auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime); - if (clientCookieStatus == LobbyCookieStatus::INVALID) + if(clientCookieStatus == LobbyCookieStatus::INVALID) return sendLoginFailed(connection, "Authentification failure"); // prolong existing cookie @@ -366,7 +366,7 @@ void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, co auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime); - if (clientCookieStatus == LobbyCookieStatus::INVALID) + if(clientCookieStatus == LobbyCookieStatus::INVALID) { sendLoginFailed(connection, "Invalid credentials"); } @@ -386,20 +386,20 @@ void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connectio auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime); - if (clientCookieStatus != LobbyCookieStatus::INVALID) + if(clientCookieStatus != LobbyCookieStatus::INVALID) { - for (auto & proxyEntry : awaitingProxies) + for(auto & proxyEntry : awaitingProxies) { - if (proxyEntry.accountID != accountID) + if(proxyEntry.accountID != accountID) continue; - if (proxyEntry.roomID != gameRoomID) + if(proxyEntry.roomID != gameRoomID) continue; proxyEntry.accountConnection = connection; auto gameRoomConnection = proxyEntry.roomConnection.lock(); - if (gameRoomConnection) + if(gameRoomConnection) { activeProxies[gameRoomConnection] = connection; activeProxies[connection] = gameRoomConnection; @@ -419,11 +419,11 @@ void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connectio auto clientCookieStatus = database->getGameRoomCookieStatus(gameRoomID, hostCookie, accountCookieLifetime); - if (clientCookieStatus != LobbyCookieStatus::INVALID) + if(clientCookieStatus != LobbyCookieStatus::INVALID) { NetworkConnectionPtr targetAccount = findAccount(guestAccountID); - if (targetAccount == nullptr) + if(targetAccount == nullptr) return; // unknown / disconnected account sendJoinRoomSuccess(targetAccount, gameRoomID); @@ -448,13 +448,13 @@ void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, c return; // only 1 room per player allowed std::string gameRoomID = database->getIdleGameRoom(hostAccountID); - if (gameRoomID.empty()) + if(gameRoomID.empty()) return; std::string roomType = json["roomType"].String(); - if (roomType == "public") + if(roomType == "public") database->setGameRoomStatus(gameRoomID, LobbyRoomState::PUBLIC); - if (roomType == "private") + if(roomType == "private") database->setGameRoomStatus(gameRoomID, LobbyRoomState::PRIVATE); // TODO: additional flags / initial settings, e.g. allowCheats @@ -474,18 +474,18 @@ void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, c NetworkConnectionPtr targetRoom = findGameRoom(gameRoomID); - if (targetRoom == nullptr) + if(targetRoom == nullptr) return; // unknown / disconnected room auto roomStatus = database->getGameRoomStatus(gameRoomID); - if (roomStatus == LobbyRoomState::PRIVATE) + if(roomStatus == LobbyRoomState::PRIVATE) { - if (database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED) + if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED) return; } - if (database->getGameRoomFreeSlots(gameRoomID) == 0) + if(database->getGameRoomFreeSlots(gameRoomID) == 0) return; sendAccountJoinsRoom(targetRoom, accountID); @@ -513,7 +513,7 @@ void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, con auto targetAccount = findAccount(accountID); - if (!targetAccount) + if(!targetAccount) return; // target player does not exists or offline if(!database->isPlayerInGameRoom(senderName)) @@ -522,7 +522,7 @@ void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, con if(database->isPlayerInGameRoom(accountID)) return; // target player is busy - if (database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::NOT_INVITED) + if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::NOT_INVITED) return; // already has invite database->insertGameRoomInvite(accountID, gameRoomID); @@ -534,7 +534,7 @@ void LobbyServer::receiveDeclineInvite(const NetworkConnectionPtr & connection, std::string accountID = activeAccounts[connection].accountID; std::string gameRoomID = json["gameRoomID"].String(); - if (database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED) + if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED) return; // already has invite database->deleteGameRoomInvite(accountID, gameRoomID); diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index 7262db0af..a7215f806 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -9,8 +9,8 @@ */ #pragma once -#include "LobbyDefines.h" #include "../lib/network/NetworkListener.h" +#include "LobbyDefines.h" VCMI_LIB_NAMESPACE_BEGIN class JsonNode; From 80e960bc8ec690a0bbb800f0482d208221831edf Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 12 Jan 2024 01:10:41 +0200 Subject: [PATCH 32/98] Finalized new TCP networking API --- client/CServerHandler.cpp | 18 ++-- client/CServerHandler.h | 13 +-- client/globalLobby/GlobalLobbyClient.cpp | 28 ++---- client/globalLobby/GlobalLobbyClient.h | 14 ++- client/gui/InterfaceObjectConfigurable.cpp | 7 +- cmake_modules/VCMI_lib.cmake | 4 +- lib/network/NetworkClient.cpp | 37 ++------ lib/network/NetworkClient.h | 22 ++--- lib/network/NetworkConnection.cpp | 2 +- lib/network/NetworkConnection.h | 7 +- lib/network/NetworkDefines.h | 4 +- lib/network/NetworkHandler.cpp | 57 +++++++++++ lib/network/NetworkHandler.h | 31 ++++++ lib/network/NetworkInterface.h | 104 +++++++++++++++++++++ lib/network/NetworkListener.h | 56 ----------- lib/network/NetworkServer.cpp | 37 ++------ lib/network/NetworkServer.h | 24 ++--- lib/serializer/Connection.cpp | 6 +- lib/serializer/Connection.h | 8 +- lobby/LobbyServer.cpp | 12 +-- lobby/LobbyServer.h | 12 +-- server/CVCMIServer.cpp | 24 ++--- server/CVCMIServer.h | 20 ++-- 23 files changed, 306 insertions(+), 241 deletions(-) create mode 100644 lib/network/NetworkHandler.cpp create mode 100644 lib/network/NetworkHandler.h create mode 100644 lib/network/NetworkInterface.h delete mode 100644 lib/network/NetworkListener.h diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 8983c9db1..a592ba61d 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -46,7 +46,6 @@ #include "../lib/mapping/CMapInfo.h" #include "../lib/mapObjects/MiscObjects.h" #include "../lib/modding/ModIncompatibility.h" -#include "../lib/network/NetworkClient.h" #include "../lib/rmg/CMapGenOptions.h" #include "../lib/serializer/Connection.h" #include "../lib/filesystem/Filesystem.h" @@ -132,15 +131,16 @@ static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") CServerHandler::~CServerHandler() { - networkClient->stop(); + networkHandler->stop(); threadNetwork->join(); } CServerHandler::CServerHandler() : state(EClientState::NONE) - , networkClient(std::make_unique(*this)) + , networkHandler(INetworkHandler::createHandler()) + , networkClient(networkHandler->createClientTCP(*this)) , applier(std::make_unique>()) - , lobbyClient(std::make_unique()) + , lobbyClient(std::make_unique(networkHandler)) , client(nullptr) , loadMode(0) , campaignStateToSend(nullptr) @@ -155,7 +155,7 @@ void CServerHandler::threadRunNetwork() { logGlobal->info("Starting network thread"); setThreadName("runNetwork"); - networkClient->run(); + networkHandler->run(); logGlobal->info("Ending network thread"); } @@ -277,7 +277,7 @@ void CServerHandler::onConnectionFailed(const std::string & errorMessage) { // retry - local server might be still starting up logNetwork->debug("\nCannot establish connection. %s. Retrying...", errorMessage); - networkClient->setTimer(std::chrono::milliseconds(100)); + networkHandler->createTimer(*this, std::chrono::milliseconds(100)); } else { @@ -299,7 +299,7 @@ void CServerHandler::onTimer() networkClient->start(getLocalHostname(), getLocalPort()); } -void CServerHandler::onConnectionEstablished(const std::shared_ptr & netConnection) +void CServerHandler::onConnectionEstablished(const std::shared_ptr & netConnection) { logNetwork->info("Connection established"); c = std::make_shared(netConnection); @@ -852,7 +852,7 @@ public: } }; -void CServerHandler::onPacketReceived(const std::shared_ptr &, const std::vector & message) +void CServerHandler::onPacketReceived(const std::shared_ptr &, const std::vector & message) { CPack * pack = c->retrievePack(message); if(state == EClientState::DISCONNECTING) @@ -868,7 +868,7 @@ void CServerHandler::onPacketReceived(const std::shared_ptr & } } -void CServerHandler::onDisconnected(const std::shared_ptr &) +void CServerHandler::onDisconnected(const std::shared_ptr &) { if(state == EClientState::DISCONNECTING) { diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 2d0ea27b3..7b761d2cc 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -11,7 +11,7 @@ #include "../lib/CStopWatch.h" -#include "../lib/network/NetworkListener.h" +#include "../lib/network/NetworkInterface.h" #include "../lib/StartInfo.h" #include "../lib/CondSh.h" @@ -82,11 +82,12 @@ public: }; /// structure to handle running server and connecting to it -class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClientListener, boost::noncopyable +class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClientListener, public INetworkTimerListener, boost::noncopyable { friend class ApplyOnLobbyHandlerNetPackVisitor; - std::unique_ptr networkClient; + std::unique_ptr networkHandler; + std::unique_ptr networkClient; std::unique_ptr lobbyClient; std::unique_ptr> applier; std::shared_ptr mapToStart; @@ -98,10 +99,10 @@ class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClien void onServerFinished(); void sendLobbyPack(const CPackForLobby & pack) const override; - void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; + void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; - void onConnectionEstablished(const std::shared_ptr &) override; - void onDisconnected(const std::shared_ptr &) override; + void onConnectionEstablished(const std::shared_ptr &) override; + void onDisconnected(const std::shared_ptr &) override; void onTimer() override; void applyPackOnLobbyScreen(CPackForLobby & pack); diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index a6af74c60..491d8f4e1 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -21,21 +21,12 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/MetaString.h" #include "../../lib/TextOperations.h" -#include "../../lib/network/NetworkClient.h" -GlobalLobbyClient::~GlobalLobbyClient() -{ - networkClient->stop(); - networkThread->join(); -} +GlobalLobbyClient::~GlobalLobbyClient() = default; -GlobalLobbyClient::GlobalLobbyClient() - : networkClient(std::make_unique(*this)) -{ - networkThread = std::make_unique([this](){ - networkClient->run(); - }); -} +GlobalLobbyClient::GlobalLobbyClient(const std::unique_ptr & handler) + : networkClient(handler->createClientTCP(*this)) +{} static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0) { @@ -46,7 +37,7 @@ static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0) return TextOperations::getFormattedTimeLocal(std::chrono::system_clock::to_time_t(timeNowChrono)); } -void GlobalLobbyClient::onPacketReceived(const std::shared_ptr &, const std::vector & message) +void GlobalLobbyClient::onPacketReceived(const std::shared_ptr &, const std::vector & message) { boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); @@ -158,7 +149,7 @@ void GlobalLobbyClient::receiveActiveAccounts(const JsonNode & json) //} } -void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr &) +void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr &) { JsonNode toSend; @@ -198,17 +189,12 @@ void GlobalLobbyClient::onConnectionFailed(const std::string & errorMessage) loginWindowPtr->onConnectionFailed(errorMessage); } -void GlobalLobbyClient::onDisconnected(const std::shared_ptr &) +void GlobalLobbyClient::onDisconnected(const std::shared_ptr &) { GH.windows().popWindows(1); CInfoWindow::showInfoDialog("Connection to game lobby was lost!", {}); } -void GlobalLobbyClient::onTimer() -{ - // no-op -} - void GlobalLobbyClient::sendMessage(const JsonNode & data) { std::string payloadString = data.toJson(true); diff --git a/client/globalLobby/GlobalLobbyClient.h b/client/globalLobby/GlobalLobbyClient.h index 335c4dc3e..92569c3dd 100644 --- a/client/globalLobby/GlobalLobbyClient.h +++ b/client/globalLobby/GlobalLobbyClient.h @@ -9,7 +9,7 @@ */ #pragma once -#include "../../lib/network/NetworkListener.h" +#include "../../lib/network/NetworkInterface.h" VCMI_LIB_NAMESPACE_BEGIN class JsonNode; @@ -20,18 +20,16 @@ class GlobalLobbyWindow; class GlobalLobbyClient : public INetworkClientListener, boost::noncopyable { - std::unique_ptr networkThread; - std::unique_ptr networkClient; + std::unique_ptr networkClient; std::weak_ptr loginWindow; std::weak_ptr lobbyWindow; std::shared_ptr lobbyWindowLock; // helper strong reference to prevent window destruction on closing - void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; + void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; - void onConnectionEstablished(const std::shared_ptr &) override; - void onDisconnected(const std::shared_ptr &) override; - void onTimer() override; + void onConnectionEstablished(const std::shared_ptr &) override; + void onDisconnected(const std::shared_ptr &) override; void sendClientRegister(); void sendClientLogin(); @@ -44,7 +42,7 @@ class GlobalLobbyClient : public INetworkClientListener, boost::noncopyable void receiveActiveAccounts(const JsonNode & json); public: - explicit GlobalLobbyClient(); + explicit GlobalLobbyClient(const std::unique_ptr & handler); ~GlobalLobbyClient(); void sendMessage(const JsonNode & data); diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index beb9a55d4..b12bb75b1 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -130,8 +130,11 @@ void InterfaceObjectConfigurable::build(const JsonNode &config) for(const auto & item : items->Vector()) addWidget(item["name"].String(), buildWidget(item)); - pos.w = config["width"].Integer(); - pos.h = config["height"].Integer(); + // load only if set + if (!config["width"].isNull()) + pos.w = config["width"].Integer(); + if (!config["height"].isNull()) + pos.h = config["height"].Integer(); } void InterfaceObjectConfigurable::addConditional(const std::string & name, bool active) diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 6ad35ac43..65549110c 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -126,6 +126,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/network/NetworkClient.cpp ${MAIN_LIB_DIR}/network/NetworkConnection.cpp + ${MAIN_LIB_DIR}/network/NetworkHandler.cpp ${MAIN_LIB_DIR}/network/NetworkServer.cpp ${MAIN_LIB_DIR}/networkPacks/NetPacksLib.cpp @@ -478,7 +479,8 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/network/NetworkClient.h ${MAIN_LIB_DIR}/network/NetworkConnection.h ${MAIN_LIB_DIR}/network/NetworkDefines.h - ${MAIN_LIB_DIR}/network/NetworkListener.h + ${MAIN_LIB_DIR}/network/NetworkHandler.h + ${MAIN_LIB_DIR}/network/NetworkInterface.h ${MAIN_LIB_DIR}/network/NetworkServer.h ${MAIN_LIB_DIR}/networkPacks/ArtifactLocation.h diff --git a/lib/network/NetworkClient.cpp b/lib/network/NetworkClient.cpp index 25462cf01..888660b3d 100644 --- a/lib/network/NetworkClient.cpp +++ b/lib/network/NetworkClient.cpp @@ -13,9 +13,9 @@ VCMI_LIB_NAMESPACE_BEGIN -NetworkClient::NetworkClient(INetworkClientListener & listener) - : io(new NetworkService) - , socket(new NetworkSocket(*io)) +NetworkClient::NetworkClient(INetworkClientListener & listener, const std::shared_ptr & context) + : io(context) + , socket(std::make_shared(*context)) , listener(listener) { } @@ -39,54 +39,29 @@ void NetworkClient::onConnected(const boost::system::error_code & ec) return; } - connection = std::make_shared(socket, *this); + connection = std::make_shared(*this, socket); connection->start(); listener.onConnectionEstablished(connection); } -void NetworkClient::run() -{ - boost::asio::executor_work_guardget_executor())> work{io->get_executor()}; - io->run(); -} - -void NetworkClient::poll() -{ - io->poll(); -} - -void NetworkClient::stop() -{ - io->stop(); -} - bool NetworkClient::isConnected() const { return connection != nullptr; } -void NetworkClient::setTimer(std::chrono::milliseconds duration) -{ - auto timer = std::make_shared(*io, duration); - timer->async_wait([this, timer](const boost::system::error_code& error){ - if (!error) - listener.onTimer(); - }); -} - void NetworkClient::sendPacket(const std::vector & message) { connection->sendPacket(message); } -void NetworkClient::onDisconnected(const std::shared_ptr & connection) +void NetworkClient::onDisconnected(const std::shared_ptr & connection) { this->connection.reset(); listener.onDisconnected(connection); } -void NetworkClient::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) +void NetworkClient::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) { listener.onPacketReceived(connection, message); } diff --git a/lib/network/NetworkClient.h b/lib/network/NetworkClient.h index cc244aa50..301d3d00d 100644 --- a/lib/network/NetworkClient.h +++ b/lib/network/NetworkClient.h @@ -10,15 +10,14 @@ #pragma once #include "NetworkDefines.h" -#include "NetworkListener.h" VCMI_LIB_NAMESPACE_BEGIN class NetworkConnection; -class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionListener +class NetworkClient : public INetworkConnectionListener, public INetworkClient { - std::shared_ptr io; + std::shared_ptr io; std::shared_ptr socket; std::shared_ptr connection; @@ -26,22 +25,17 @@ class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionL void onConnected(const boost::system::error_code & ec); - void onDisconnected(const std::shared_ptr & connection) override; - void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; + void onDisconnected(const std::shared_ptr & connection) override; + void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; public: - NetworkClient(INetworkClientListener & listener); - virtual ~NetworkClient() = default; + NetworkClient(INetworkClientListener & listener, const std::shared_ptr & context); - bool isConnected() const; + bool isConnected() const override; - void setTimer(std::chrono::milliseconds duration); - void sendPacket(const std::vector & message); + void sendPacket(const std::vector & message) override; - void start(const std::string & host, uint16_t port); - void run(); - void poll(); - void stop(); + void start(const std::string & host, uint16_t port) override; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index 7c068147b..b2fce0160 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -12,7 +12,7 @@ VCMI_LIB_NAMESPACE_BEGIN -NetworkConnection::NetworkConnection(const std::shared_ptr & socket, INetworkConnectionListener & listener) +NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr & socket) : socket(socket) , listener(listener) { diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h index ab315c7c5..afdc7d967 100644 --- a/lib/network/NetworkConnection.h +++ b/lib/network/NetworkConnection.h @@ -10,11 +10,10 @@ #pragma once #include "NetworkDefines.h" -#include "NetworkListener.h" VCMI_LIB_NAMESPACE_BEGIN -class DLL_LINKAGE NetworkConnection :public std::enable_shared_from_this, boost::noncopyable +class NetworkConnection : public INetworkConnection, public std::enable_shared_from_this { static const int messageHeaderSize = sizeof(uint32_t); static const int messageMaxSize = 64 * 1024 * 1024; // arbitrary size to prevent potential massive allocation if we receive garbage input @@ -29,10 +28,10 @@ class DLL_LINKAGE NetworkConnection :public std::enable_shared_from_this & socket, INetworkConnectionListener & listener); + NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr & socket); void start(); - void sendPacket(const std::vector & message); + void sendPacket(const std::vector & message) override; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkDefines.h b/lib/network/NetworkDefines.h index 0dce17821..6b86ff23a 100644 --- a/lib/network/NetworkDefines.h +++ b/lib/network/NetworkDefines.h @@ -11,9 +11,11 @@ #include +#include "NetworkInterface.h" + VCMI_LIB_NAMESPACE_BEGIN -using NetworkService = boost::asio::io_service; +using NetworkContext = boost::asio::io_service; using NetworkSocket = boost::asio::ip::tcp::socket; using NetworkAcceptor = boost::asio::ip::tcp::acceptor; using NetworkBuffer = boost::asio::streambuf; diff --git a/lib/network/NetworkHandler.cpp b/lib/network/NetworkHandler.cpp new file mode 100644 index 000000000..ed5503f73 --- /dev/null +++ b/lib/network/NetworkHandler.cpp @@ -0,0 +1,57 @@ +/* + * NetworkHandler.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "NetworkHandler.h" + +#include "NetworkClient.h" +#include "NetworkServer.h" + +VCMI_LIB_NAMESPACE_BEGIN + +std::unique_ptr INetworkHandler::createHandler() +{ + return std::make_unique(); +} + +NetworkHandler::NetworkHandler() + : io(std::make_shared()) +{} + +std::unique_ptr NetworkHandler::createServerTCP(INetworkServerListener & listener) +{ + return std::make_unique(listener, io); +} + +std::unique_ptr NetworkHandler::createClientTCP(INetworkClientListener & listener) +{ + return std::make_unique(listener, io); +} + +void NetworkHandler::run() +{ + boost::asio::executor_work_guardget_executor())> work{io->get_executor()}; + io->run(); +} + +void NetworkHandler::createTimer(INetworkTimerListener & listener, std::chrono::milliseconds duration) +{ + auto timer = std::make_shared(*io, duration); + timer->async_wait([&listener, timer](const boost::system::error_code& error){ + if (!error) + listener.onTimer(); + }); +} + +void NetworkHandler::stop() +{ + io->stop(); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkHandler.h b/lib/network/NetworkHandler.h new file mode 100644 index 000000000..d41933869 --- /dev/null +++ b/lib/network/NetworkHandler.h @@ -0,0 +1,31 @@ +/* + * NetworkHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "NetworkDefines.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class NetworkHandler : public INetworkHandler +{ + std::shared_ptr io; + +public: + NetworkHandler(); + + std::unique_ptr createServerTCP(INetworkServerListener & listener) override; + std::unique_ptr createClientTCP(INetworkClientListener & listener) override; + void createTimer(INetworkTimerListener & listener, std::chrono::milliseconds duration) override; + + void run() override; + void stop() override; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkInterface.h b/lib/network/NetworkInterface.h new file mode 100644 index 000000000..4d3072992 --- /dev/null +++ b/lib/network/NetworkInterface.h @@ -0,0 +1,104 @@ +/* + * NetworkHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + +/// Base class for connections with other services, either incoming or outgoing +class DLL_LINKAGE INetworkConnection : boost::noncopyable +{ +public: + virtual ~INetworkConnection() = default; + virtual void sendPacket(const std::vector & message) = 0; +}; + +using NetworkConnectionPtr = std::shared_ptr; +using NetworkConnectionWeakPtr = std::weak_ptr; + +/// Base class for outgoing connections support +class DLL_LINKAGE INetworkClient : boost::noncopyable +{ +public: + virtual ~INetworkClient() = default; + + virtual bool isConnected() const = 0; + virtual void sendPacket(const std::vector & message) = 0; + virtual void start(const std::string & host, uint16_t port) = 0; +}; + +/// Base class for incoming connections support +class DLL_LINKAGE INetworkServer : boost::noncopyable +{ +public: + virtual ~INetworkServer() = default; + + virtual void sendPacket(const std::shared_ptr &, const std::vector & message) = 0; + virtual void closeConnection(const std::shared_ptr &) = 0; + virtual void start(uint16_t port) = 0; +}; + +/// Base interface that must be implemented by user of networking API to handle any connection callbacks +class DLL_LINKAGE INetworkConnectionListener +{ +public: + virtual void onDisconnected(const std::shared_ptr & connection) = 0; + virtual void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) = 0; + + virtual ~INetworkConnectionListener() = default; +}; + +/// Interface that must be implemented by user of networking API to handle outgoing connection callbacks +class DLL_LINKAGE INetworkClientListener : public INetworkConnectionListener +{ +public: + virtual void onConnectionFailed(const std::string & errorMessage) = 0; + virtual void onConnectionEstablished(const std::shared_ptr &) = 0; +}; + +/// Interface that must be implemented by user of networking API to handle incoming connection callbacks +class DLL_LINKAGE INetworkServerListener : public INetworkConnectionListener +{ +public: + virtual void onNewConnection(const std::shared_ptr &) = 0; +}; + +/// Interface that must be implemented by user of networking API to handle timers on network thread +class DLL_LINKAGE INetworkTimerListener +{ +public: + virtual ~INetworkTimerListener() = default; + + virtual void onTimer() = 0; +}; + +/// Main class for handling of all network activity +class DLL_LINKAGE INetworkHandler : boost::noncopyable +{ +public: + virtual ~INetworkHandler() = default; + + /// Constructs default implementation + static std::unique_ptr createHandler(); + + /// Creates an instance of TCP server that allows to receiving connections on a local port + virtual std::unique_ptr createServerTCP(INetworkServerListener & listener) = 0; + + /// Creates an instance of TCP client that allows to establish single outgoing connection to a remote port + virtual std::unique_ptr createClientTCP(INetworkClientListener & listener) = 0; + + /// Creates a timer that will be called once, after specified interval has passed + virtual void createTimer(INetworkTimerListener & listener, std::chrono::milliseconds duration) = 0; + + /// Starts network processing on this thread. Does not returns until networking processing has been terminated + virtual void run() = 0; + virtual void stop() = 0; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkListener.h b/lib/network/NetworkListener.h deleted file mode 100644 index f1ee7885d..000000000 --- a/lib/network/NetworkListener.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * NetworkListener.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -VCMI_LIB_NAMESPACE_BEGIN - -class NetworkConnection; -class NetworkServer; -class NetworkClient; - -using NetworkConnectionPtr = std::shared_ptr; -using NetworkConnectionWeakPtr = std::weak_ptr; - -class DLL_LINKAGE INetworkConnectionListener -{ - friend class NetworkConnection; -protected: - virtual void onDisconnected(const std::shared_ptr & connection) = 0; - virtual void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) = 0; - -public: - virtual ~INetworkConnectionListener() = default; -}; - -class DLL_LINKAGE INetworkServerListener : public INetworkConnectionListener -{ - friend class NetworkServer; -protected: - virtual void onNewConnection(const std::shared_ptr &) = 0; - virtual void onTimer() = 0; - -public: - virtual ~INetworkServerListener() = default; -}; - -class DLL_LINKAGE INetworkClientListener : public INetworkConnectionListener -{ - friend class NetworkClient; -protected: - virtual void onConnectionFailed(const std::string & errorMessage) = 0; - virtual void onConnectionEstablished(const std::shared_ptr &) = 0; - virtual void onTimer() = 0; - -public: - virtual ~INetworkClientListener() = default; -}; - - -VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp index e96cae41e..546d0b652 100644 --- a/lib/network/NetworkServer.cpp +++ b/lib/network/NetworkServer.cpp @@ -13,16 +13,15 @@ VCMI_LIB_NAMESPACE_BEGIN -NetworkServer::NetworkServer(INetworkServerListener & listener) - :listener(listener) +NetworkServer::NetworkServer(INetworkServerListener & listener, const std::shared_ptr & context) + : listener(listener) + , io(context) { } void NetworkServer::start(uint16_t port) { - io = std::make_shared(); acceptor = std::make_shared(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)); - startAsyncAccept(); } @@ -32,16 +31,6 @@ void NetworkServer::startAsyncAccept() acceptor->async_accept(*upcomingConnection, std::bind(&NetworkServer::connectionAccepted, this, upcomingConnection, _1)); } -void NetworkServer::run() -{ - io->run(); -} - -void NetworkServer::run(std::chrono::milliseconds duration) -{ - io->run_for(duration); -} - void NetworkServer::connectionAccepted(std::shared_ptr upcomingConnection, const boost::system::error_code & ec) { if(ec) @@ -50,44 +39,34 @@ void NetworkServer::connectionAccepted(std::shared_ptr upcomingCo } logNetwork->info("We got a new connection! :)"); - auto connection = std::make_shared(upcomingConnection, *this); + auto connection = std::make_shared(*this, upcomingConnection); connections.insert(connection); connection->start(); listener.onNewConnection(connection); startAsyncAccept(); } -void NetworkServer::sendPacket(const std::shared_ptr & connection, const std::vector & message) +void NetworkServer::sendPacket(const std::shared_ptr & connection, const std::vector & message) { connection->sendPacket(message); } -void NetworkServer::closeConnection(const std::shared_ptr & connection) +void NetworkServer::closeConnection(const std::shared_ptr & connection) { assert(connections.count(connection)); connections.erase(connection); } -void NetworkServer::onDisconnected(const std::shared_ptr & connection) +void NetworkServer::onDisconnected(const std::shared_ptr & connection) { assert(connections.count(connection)); connections.erase(connection); listener.onDisconnected(connection); } -void NetworkServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) +void NetworkServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) { listener.onPacketReceived(connection, message); } -void NetworkServer::setTimer(std::chrono::milliseconds duration) -{ - auto timer = std::make_shared(*io, duration); - timer->async_wait([this, timer](const boost::system::error_code& error){ - if (!error) - listener.onTimer(); - }); -} - - VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkServer.h b/lib/network/NetworkServer.h index 363032ee1..4c1c57aa2 100644 --- a/lib/network/NetworkServer.h +++ b/lib/network/NetworkServer.h @@ -10,35 +10,29 @@ #pragma once #include "NetworkDefines.h" -#include "NetworkListener.h" VCMI_LIB_NAMESPACE_BEGIN -class NetworkConnection; - -class DLL_LINKAGE NetworkServer : boost::noncopyable, public INetworkConnectionListener +class NetworkServer : public INetworkConnectionListener, public INetworkServer { - std::shared_ptr io; + std::shared_ptr io; std::shared_ptr acceptor; - std::set> connections; + std::set> connections; INetworkServerListener & listener; void connectionAccepted(std::shared_ptr, const boost::system::error_code & ec); void startAsyncAccept(); - void onDisconnected(const std::shared_ptr & connection) override; - void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; + void onDisconnected(const std::shared_ptr & connection) override; + void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; public: - explicit NetworkServer(INetworkServerListener & listener); + NetworkServer(INetworkServerListener & listener, const std::shared_ptr & context); - void sendPacket(const std::shared_ptr &, const std::vector & message); - void closeConnection(const std::shared_ptr &); - void setTimer(std::chrono::milliseconds duration); + void sendPacket(const std::shared_ptr &, const std::vector & message) override; + void closeConnection(const std::shared_ptr &) override; - void start(uint16_t port); - void run(std::chrono::milliseconds duration); - void run(); + void start(uint16_t port) override; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index ad0dfd852..8005e947c 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -14,7 +14,7 @@ #include "BinarySerializer.h" #include "../networkPacks/NetPacksBase.h" -#include "../network/NetworkConnection.h" +#include "../network/NetworkInterface.h" VCMI_LIB_NAMESPACE_BEGIN @@ -55,7 +55,7 @@ int ConnectionPackReader::read(void * data, unsigned size) return size; } -CConnection::CConnection(std::weak_ptr networkConnection) +CConnection::CConnection(std::weak_ptr networkConnection) : networkConnection(networkConnection) , packReader(std::make_unique()) , packWriter(std::make_unique()) @@ -100,7 +100,7 @@ CPack * CConnection::retrievePack(const std::vector & data) return result; } -bool CConnection::isMyConnection(const std::shared_ptr & otherConnection) const +bool CConnection::isMyConnection(const std::shared_ptr & otherConnection) const { return otherConnection != nullptr && networkConnection.lock() == otherConnection; } diff --git a/lib/serializer/Connection.h b/lib/serializer/Connection.h index 7d5f307b7..5c739b955 100644 --- a/lib/serializer/Connection.h +++ b/lib/serializer/Connection.h @@ -14,7 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN class BinaryDeserializer; class BinarySerializer; struct CPack; -class NetworkConnection; +class INetworkConnection; class ConnectionPackReader; class ConnectionPackWriter; class CGameState; @@ -24,7 +24,7 @@ class CGameState; class DLL_LINKAGE CConnection : boost::noncopyable { /// Non-owning pointer to underlying connection - std::weak_ptr networkConnection; + std::weak_ptr networkConnection; std::unique_ptr packReader; std::unique_ptr packWriter; @@ -39,12 +39,12 @@ class DLL_LINKAGE CConnection : boost::noncopyable void enableSmartVectorMemberSerializatoin(); public: - bool isMyConnection(const std::shared_ptr & otherConnection) const; + bool isMyConnection(const std::shared_ptr & otherConnection) const; std::string uuid; int connectionID; - CConnection(std::weak_ptr networkConnection); + CConnection(std::weak_ptr networkConnection); ~CConnection(); void sendPack(const CPack * pack); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 52c8f5e49..6f079e41f 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -13,8 +13,6 @@ #include "LobbyDatabase.h" #include "../lib/JsonNode.h" -#include "../lib/network/NetworkConnection.h" -#include "../lib/network/NetworkServer.h" #include #include @@ -199,11 +197,6 @@ void LobbyServer::sendChatMessage(const NetworkConnectionPtr & target, const std sendMessage(target, reply); } -void LobbyServer::onTimer() -{ - // no-op -} - void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection) { // no-op - waiting for incoming data @@ -544,7 +537,8 @@ LobbyServer::~LobbyServer() = default; LobbyServer::LobbyServer(const boost::filesystem::path & databasePath) : database(new LobbyDatabase(databasePath)) - , networkServer(new NetworkServer(*this)) + , networkHandler(INetworkHandler::createHandler()) + , networkServer(networkHandler->createServerTCP(*this)) { } @@ -555,5 +549,5 @@ void LobbyServer::start(uint16_t port) void LobbyServer::run() { - networkServer->run(); + networkHandler->run(); } diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index a7215f806..640cad245 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -9,7 +9,7 @@ */ #pragma once -#include "../lib/network/NetworkListener.h" +#include "../lib/network/NetworkInterface.h" #include "LobbyDefines.h" VCMI_LIB_NAMESPACE_BEGIN @@ -35,12 +35,12 @@ class LobbyServer : public INetworkServerListener { std::string accountID; std::string roomID; - std::weak_ptr accountConnection; - std::weak_ptr roomConnection; + std::weak_ptr accountConnection; + std::weak_ptr roomConnection; }; /// list of connected proxies. All messages received from (key) will be redirected to (value) connection - std::map> activeProxies; + std::map> activeProxies; /// list of half-established proxies from server that are still waiting for client to connect std::vector awaitingProxies; @@ -52,7 +52,8 @@ class LobbyServer : public INetworkServerListener std::map activeGameRooms; std::unique_ptr database; - std::unique_ptr networkServer; + std::unique_ptr networkHandler; + std::unique_ptr networkServer; std::string sanitizeChatMessage(const std::string & inputString) const; bool isAccountNameValid(const std::string & accountName); @@ -63,7 +64,6 @@ class LobbyServer : public INetworkServerListener void onNewConnection(const NetworkConnectionPtr & connection) override; void onDisconnected(const NetworkConnectionPtr & connection) override; void onPacketReceived(const NetworkConnectionPtr & connection, const std::vector & message) override; - void onTimer() override; void sendMessage(const NetworkConnectionPtr & target, const JsonNode & json); diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index bb677deae..b9e3ee66d 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -36,8 +36,6 @@ #include "CGameHandler.h" #include "processors/PlayerMessageProcessor.h" #include "../lib/mapping/CMapInfo.h" -#include "../lib/network/NetworkServer.h" -#include "../lib/network/NetworkClient.h" #include "../lib/GameConstants.h" #include "../lib/logging/CBasicLogConfigurator.h" #include "../lib/CConfigHandler.h" @@ -164,14 +162,15 @@ CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts) port = cmdLineOptions["port"].as(); logNetwork->info("Port %d will be used", port); - networkServer = std::make_unique(*this); + networkHandler = INetworkHandler::createHandler(); + networkServer = networkHandler->createServerTCP(*this); networkServer->start(port); logNetwork->info("Listening for connections at port %d", port); } CVCMIServer::~CVCMIServer() = default; -void CVCMIServer::onNewConnection(const std::shared_ptr & connection) +void CVCMIServer::onNewConnection(const std::shared_ptr & connection) { if (activeConnections.empty()) establishOutgoingConnection(); @@ -187,7 +186,7 @@ void CVCMIServer::onNewConnection(const std::shared_ptr & con } } -void CVCMIServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) +void CVCMIServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) { std::shared_ptr c = findConnection(connection); auto pack = c->retrievePack(message); @@ -201,7 +200,7 @@ void CVCMIServer::onConnectionFailed(const std::string & errorMessage) //TODO: handle failure to connect to lobby } -void CVCMIServer::onConnectionEstablished(const std::shared_ptr &) +void CVCMIServer::onConnectionEstablished(const std::shared_ptr &) { //TODO: handle connection to lobby - login? } @@ -216,7 +215,7 @@ EServerState CVCMIServer::getState() const return state; } -std::shared_ptr CVCMIServer::findConnection(const std::shared_ptr & netConnection) +std::shared_ptr CVCMIServer::findConnection(const std::shared_ptr & netConnection) { for (auto const & gameConnection : activeConnections) { @@ -236,7 +235,7 @@ void CVCMIServer::run() vmHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady"); } #endif - networkServer->run(); + networkHandler->run(); } void CVCMIServer::onTimer() @@ -258,7 +257,7 @@ void CVCMIServer::onTimer() if (msDelta.count()) gh->tick(msDelta.count()); - networkServer->setTimer(serverUpdateInterval); + networkHandler->createTimer(*this, serverUpdateInterval); } void CVCMIServer::establishOutgoingConnection() @@ -269,7 +268,7 @@ void CVCMIServer::establishOutgoingConnection() std::string hostname = settings["lobby"]["hostname"].String(); int16_t port = settings["lobby"]["port"].Integer(); - outgoingConnection = std::make_unique(*this); + outgoingConnection = networkHandler->createClientTCP(*this); outgoingConnection->start(hostname, port); } @@ -370,7 +369,7 @@ void CVCMIServer::startGameImmediately() onTimer(); } -void CVCMIServer::onDisconnected(const std::shared_ptr & connection) +void CVCMIServer::onDisconnected(const std::shared_ptr & connection) { logNetwork->error("Network error receiving a pack. Connection has been closed"); @@ -998,7 +997,8 @@ static void handleCommandOptions(int argc, const char * argv[], boost::program_o ("help,h", "display help and exit") ("version,v", "display version information and exit") ("run-by-client", "indicate that server launched by client on same machine") - ("port", po::value(), "port at which server will listen to connections from client"); + ("port", po::value(), "port at which server will listen to connections from client") + ("lobby", "start server in lobby mode in which server connects to a global lobby"); if(argc > 1) { diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 030e44e81..346d135d7 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -9,7 +9,7 @@ */ #pragma once -#include "../lib/network/NetworkListener.h" +#include "../lib/network/NetworkInterface.h" #include "../lib/StartInfo.h" #include @@ -47,13 +47,15 @@ enum class EServerState : ui8 SHUTDOWN }; -class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INetworkClientListener +class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INetworkClientListener, public INetworkTimerListener { + std::unique_ptr networkHandler; + /// Network server instance that receives and processes incoming connections on active socket - std::unique_ptr networkServer; + std::unique_ptr networkServer; /// Outgoing connection established by this server to game lobby for proxy mode (only in lobby game) - std::unique_ptr outgoingConnection; + std::unique_ptr outgoingConnection; std::chrono::steady_clock::time_point gameplayStartTime; std::chrono::steady_clock::time_point lastTimerUpdateTime; @@ -70,16 +72,16 @@ private: EServerState state; // INetworkListener impl - void onDisconnected(const std::shared_ptr & connection) override; - void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; - void onNewConnection(const std::shared_ptr &) override; + void onDisconnected(const std::shared_ptr & connection) override; + void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; + void onNewConnection(const std::shared_ptr &) override; void onConnectionFailed(const std::string & errorMessage) override; - void onConnectionEstablished(const std::shared_ptr &) override; + void onConnectionEstablished(const std::shared_ptr &) override; void onTimer() override; void establishOutgoingConnection(); - std::shared_ptr findConnection(const std::shared_ptr &); + std::shared_ptr findConnection(const std::shared_ptr &); int currentClientId; ui8 currentPlayerId; From 9fb7d2817a4b867fb1016dd2b1608f335e7241cd Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 12 Jan 2024 13:30:53 +0200 Subject: [PATCH 33/98] Implemented connection of match server to global lobby --- launcher/icons/menu-lobby.png | Bin 46771 -> 0 bytes launcher/icons/room-private.png | Bin 683 -> 0 bytes server/CMakeLists.txt | 2 + server/CVCMIServer.cpp | 29 +++-------- server/CVCMIServer.h | 13 ++--- server/GlobalLobbyProcessor.cpp | 82 ++++++++++++++++++++++++++++++++ server/GlobalLobbyProcessor.h | 39 +++++++++++++++ 7 files changed, 134 insertions(+), 31 deletions(-) delete mode 100644 launcher/icons/menu-lobby.png delete mode 100644 launcher/icons/room-private.png create mode 100644 server/GlobalLobbyProcessor.cpp create mode 100644 server/GlobalLobbyProcessor.h diff --git a/launcher/icons/menu-lobby.png b/launcher/icons/menu-lobby.png deleted file mode 100644 index 6385fc3b587dbd6403a3e72656d0ec66380d477a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46771 zcmZr%WmpvN+nrsO?hq7dkWfmxLqIyDQDBLmfOJa3(j_GdNF&`KQc_EYbeFJncP+8( z%m4fPY36#a>zSFS?m724=Z@0SP$a~o#sdI=P+3V{8vsC$w;%uq>+xdjT5JUX^*+k- zvbtWNgQ=j$HUOM+okF(}Tnuu=1t0(pKmsrTG63vm^YQf{zia+QvJDp#87H}W|>pmHEVNcG;;UT1YMn`Q??Ub-83%$eUAb4^r5ikA)`(86tx~cMJ zR)k;;@cK4-;Zg6xwH z{PYt0Y2#5C>ia#1a5hA6*5%m&eNI(h)sg9K^WE<5&em;#oqL1F8uEOx8}lKrv|V`k zR@!m7r6fBd^Kl$Am8czUJ^y$dr?%;$i^7k5E-J5dw$(n~ht_QLT7Me&ZN_#GX{7j} zI4!lBc$n_BPtIza4372caw*o6BV1<{qE?eu_j->Bm%7YX*4!?KYu_0J91}r^_;5z) z`Iei-#Ypc|FvHd+586gulh_Fh#ZVob-d)dS#ho$mrD-x%L~wNO{%Lurdg$0i?z`;Y zbccD?sA#EA&~C&k0fRC9bpQ8|A{mcxZSL>8Ei)aLF|&<+TTE1%SzR3BOJHmTq->4h zZO_p^X=)}`A{aK3uU#}zsRaYcIX0-CK$~QB^mTs7R3v7ApKUFW4u_EPwU^E8SVkz@ zS!9XiXr8Vs2Zbty>c@6JE-{Mxwv8&$I7QZJCGM1;g}qom-|EMP%S`PgR(nkhJ^uvu zpiQZNb!aT+om?BCG4XG?M}4BKw4K65*x}!8$lq=Wb`VLe)i7J()9^b{J%4enK0>}q z!M>&MG5wmuN#$i9Z5q3sYn1rRYu#4~n?bpKf^9In8M(M)m5)##q#O zagpvj3JNQArx()PvEdXMd&%<;b0`py?@j1Iz9CqkmJ2grSaAz)9ve=tGA|qhfM^M>PDeG zLzw(ETuE0)QbhFP^rHOwq;gMF9L%r2;3k?mn{4`6OH)<3yB@h>r!4+?M#H`C9UU2r z1I7t9hxk`hD$V_r$BBKGEobhd3 zO`n~eu-X$hA3BGW1(Bv>UTY$nE;mU>7+*iV+T_h38jVKrcP~`{k>1Pa%ZZ#6)Y310 zLLt;%l+@{a-pj9qe%WmLVW|>@gkXl_y$BD-*Yt6)icHWDO#Ca5s4Q%5WMy`6)Mh?% z7cT8ql1Xk|sq-==k2^WBwg$PUniGjdP4gxPBwM8$wWPxC1(fDl=~BGa{Z42{Ikq$v zX&~A%)@be=YWJO_xCV75_=ER#C>y41v1au(rPf#4?N;Qm(6t&d{}JbTrT)-=AClGh zYH1fw@#i(xoY%G`wo$ZRBm$wdd>cKo{DZ&D3?`yK*R_b(b`9|lJEJfCi*Cg|_KZC= z?G)|e^UpX8Om=1t4)5;#jSIGWug6(x(qz!5wIZmUNu|;oYU(HaD03!KRLFWC+KFa= z(Ub|6a&RBtq7OoA0`{^$W(xB+53iTLNE#e3*nyotX%vPjgX=YAMldmmm}I4YGD^q8l%K+GXvgyN+9NU}EC*e@)=qR% z(^Gq?7aG?Qo<#JH56=R`d+HV6lc#5N zFRMp`fb2gy8R9-fKpF%g6VHu=*9`$T(uNa;z<~eti2B^rE3?+G$w5H<7 z0GfvEpFx4oz$Bc0=jriS4?FP>m&`xZj@Ac-2&twms%GXF;|$t2pUjnYdX>0SD#m9v zB$Yc`+0lUr!-_}-O59)b zh)x#ziZH|X3PF*GHTF2^6ZS1Os~H5lyY%y@M*3#-!)WDNLGQ44kg9k4Q2`FTCMzP! z{@Ob5O0()24Gmi73G>QswEB=qhMs`ap-u1pvJ3H*NezY+CW&p?Da96LviBFZiAXk` z$WS?v-%c^qAq2=qZ~*JJIx9uJ?}qZnyR7HQZV)e5_zeM>v!4{GWd@Icsags4R_E^8 zLPG9PsAFb)ITYn96e6n(E3#8S$X)scjhyliJdt@HL-<*9z;Q(mqD4;lla&l1J<)Xr{%+Qjr7HEd?9R{x>iiqhSF8sYpI=n&P6(?{z80QQ zMG=>#tQdSlQlEey62KRdPJW`5&6g^WV9U;^>x1I>~sS(G` zq+&l&O_1>hBmcJF56;lvBYsE^Sn>PXNA={4;WV9vhcxalFOrl+1&wHV3cwDF(*HIa zs@FIW&&nFjhKQ0lbNC)JDu6B5YYBWJI*x7lPGr>pbu1G8oPNchm`Vb0K{Out`znwV zBwMJ=K#jZJYxlX!gNChlr+o?KT=1K}nkz}tlxESxQ;<#D> z!Za^WZ5>b7chlGE&-S(%2f6IO(VtBl5CsU;m(q{@XA+detD zr~<;O6U%=VJRQcj*|+U!Rrz;(;24IFy=za2iFu;L!h%pP_>~HM-_w1;=X3YRa5$oN zCW2c^6!a=SF6N;_S>kHS=)t!e;_SCT51eAB?zK|`)SJ@W@@6(P7UzGPg3ZtD7bl|B zO&t6x0Z!Q=WX}_K$@iGQV@p!ST>I}%nYWJvfTG^o@D=etnRPLt=e zdy82eqK-ti5i=`CA-b%E2{O|O&+t^8E%2H(VkM~;&{E>!oq0&EfpbfP4OW& zAe#L7s*_zK<5{Ss0=SVM$>Ci1*oARSA`#1z2}dTR9r*D9!oUhT>&+Mn zvKGX|{LhOw5v}mN4mSY0Cazw$ejH!`M*(+ z-^=c8D$7K|x6ScHnrQ9=krB|_AE&2H!m9ci3V;V;UE|ise!|pW%Xbb^1zYukv$Yr_ z)3}0tqp_$Ue=BjP)WElHS;*@o5BFAZ4!?N;15N-hNA2;=}K|%iP?fsQN znjdQVu3)dJ(sm4G!Wr5#S_%HHAS|j>hO(-P-sOM(TcX6r(%DI&IW)19xLPkmmBU3V zXulg=w+eTygq0|i6k1))H za-LSMR(&T&%Gv%4I#7k2tgmkhckoyiyoVngsBAnglysn_%jClr#%GVhN)2aUE0m;p}l3 z;;XEaijN{pbJ!YjoqjEuiwiFjdwL=<3lpXd&UxodEOnf=_r-ihKybzx(?U;x3Akhd z#3&=^Gp(x^(_jgV!?icTC4}EKMSL?>UKbD^LqQE}Z<)z)oi969oiZe3h0rt4e?z95p+e zm+#_3kq>=I0~eo9L*;N_9d7>|49rJ^4wYCY8+iqA9Vv0@?RVdHoaQB9)A`;?kcMsG zPyjS@t+>AR*Jk#%ASx`jCN^y&RxC!)zkd3YB&R@83q8ifDX?poucou$fSLD;AbL7c z5PZ4`kP`WcOOYA0M-%G~xZqH;Ff^Jcxc@$*1~P{JTigi?{!Eevfiqt$%>X%qjEP7A z>i@JD_;`n-t@W<0t*KnL?nx*Z!m85tN~;TMK_(S~J2v{QC0Guz)5R4;d2cg$<=iJN+mr+rA5iM7A!*%TStNc~SNA~#B5$@^S3 z;3i(mLpI1yL)YZ1$Skx8KbG)yO_2#NiSyb44qd=KK?IHi*^Yfh-}E3=wZ9uSI3PP0 zv?CGVKvYfP8T8!?Of32NJsWn-lrRiB1!m|2->=ccv2Z2me5FL&l*dO=nr$DaF9-g> zAQ5ku*;cPxaYpXCHT_inHp>j%UbprD7xIZ_NDAW!r*|w5SIjlnJ!uvaw6rt8*s~!0 z=7fcNT3lDNW=uY|*U48EEg&X_ze(P?JK3d%_ea}}#@RG4BSf=R9!ObPKk+EV1~Wf<~pbiu8q=K!d8}EWqj)$z;JAxE-ZgbcJzs#mq0-c zmZ+x-c+i^>oJ8^TBK$&l3oMUIO#L~q|5#2@?y0&tmBI0m)>p~!?FrWlln*A$EED6j zAjj>Lq&MgA5HqP}bzl%oGa$^e59{RHZDvs+B_(L3jK^tWOclbA(@sI=bxq>~!xXmmuhW4Z~P>}XOn{skQ(UD#BPBoBKdmPIc2IpCbnjm;BUJIL= z;5+*rO-3#o7Z~`RCcKYcI(_dr+v{Ime85&S4=Mbk@);>Sg$;y(>|H{S$+2BYA`z!h z?G>sQyd-04gk~wOSC@xN@)KEbhRxHx(&C&v{#X4QXBKx?9t~fIdG?s+M|hfVU{9wo z8>#Q(rsjF7SmzDKre*)y)zvykHCG2aEukr_tSAR+`c)k~>V#jOK%>|{KCvB3AL)3* z9q)ubdD@;D^mA{N1;LLO#_#~s8XGlp&~MGHPaDPUFe-yg?$q)2*)l1h-;UVRP-mKx zvyu`1eW`#+^>$f06yo}ofW@}GAR0qmknv!rv}2~;mKAnsgK3bYQpv!QpqID49;U~F z!Dqe6!Lr7YUn^?r$tcwf^JD#sVEZ+^{fUJsbudd>`YMRWZ`Cq8Fbc?mPD|T^+O)}V zBu?P`*hKVFr|&Q?NI-IH8xq&6W0!a1Q_G=0=3nrOYJe?OPX!98*4QI`@azxmtOqIP6SMo!y7L zU`p24F^ns^IgV!ISCjZ}OZQdq?w+lzrTPp1%SXeu6G3Zz)53J} z<+;g!JwTwy{+{_BNb)*|95r}R$kUk!&iu%w+48A}aTB~b!$x9&Wb6YX|1&oHvYxIo z$fo^_3lsn6*S53zSKlV9lB8=JzsdNUJh@jW;d$*_8$BAPsoHo)BpqTbWEYgJ=s%LH z1WAPX>C*%<-EVsT<6mo;&y%g6Bj4~)0t?>twyGNm0m z^M^M}H9Za5!;hL2k(P*fsKI1$C9>Unt_93Fxm`r*Xt%J45>k$nHaY&A>9aECB$Ls! zBLs(WmV&v(((SaKm?CBY|GhAUoy z#qJHi!DZPZud6pdtoW?W0u9j)`!Fc%m>B6itR6uUrFZ&kK5oXhb4bhc$He3YF`$;@Oy6`}?CjBHAP?@r34w_R4`wO6*W^}??2@^x5O>KbU? zuv2$P1g#AIV%GDpV$=}IoBLW*$}W0cOrzh`T{E*b>vlv`Xq_@wOJsl}I~2i>S{LzY zq5q*47v#Da>-&9S1uL%i*?>|w7K6N5@XGk7KQy@gn??)R@|Q*Kb0n2>lZrfbeepvR z&yAP<5S^!(0dvY)2*CR)Ee*@=2b z@MYBZ$bD*jAvCl37D4?tUh-@Xw<~Brc?!HkHvudxeE6m=Jz_^_zia!S8+`gYNn>ta zJH+8%>`5N1spIv=S7pKgrc)-|#;A11h3oOzwyW?_CwVgG0JVa4+=r9*%4YWV^_zd8 zZRmC?JVy*=|7k(ww8YN#%Qk1dZ}^^mWayL>VmDP>ZI2hCMXeQ}SdZ6mW1+tZsRWGc zn;9m?@9H0nu==xTTWuVR!|aw`A^1$DtyJ1|e8QAeGY0$sL zKjVf_^%d5@0wc7JLi=T6&TigGm>{wvK24oW7yY)X`|+n0x^+9c$%CCY1UNF;fU zW8+tb23IKL+~o$gUo~F@(C{UNos4z$%_*9b0V+12E3$)&1EE&~^f94Yyhol>g#Qjb zcI=BgI}wpgpbzBzlb&2FZlkfs&38wy+P6eBt{#tz@GeiD%W^s-Ifc9qjf`KpA$)=9 zgM<9Tj|{=PH+UHVEw2pMGs=9%24^}MQLul3<)&n z;LdW4uBF%g+W6-s*dQr_1h%ds)ox@6F;D`e{&bXr5-ed|PVrJML`{AgN;wrx$w^Tc z6@|*HC4_KQ3>a*g1#I_(0SCGJTJRM@csAUS+(rI?E5slt?=i^BbMfyJ`>zs;Mdl5K+M{X;(H{ZW^%krE+U4~7k1gC2@ zJQ<$2f**RmQ?#>e@Y>mxYSp*ENlxIq+Z^lLA301Te&tsY zYT!2tyupzv0{_Lw|Dry$zIMWb_VrzOKK^e!e@n{cUUcEqn*2?q(CG-FencvXRlYxg-py{i3;15*AwG|Uc#+@V z=>==**fDPzor!;?Q*|48c&}bt8^IEwr#BbC0R9XrSHOW_4EBrPcN;B7$JCBYxl}T^ z*wDl(b?*13uzuq_GgjEey`HvFtLu@wc=W z!v!D23=l7dA_)#SWo$jhfyJ9mh}8CJRlbXhSckTBQ62L@zYeM`BJPoA*x3HAC)sr+ zsQ6r>4z^q=WdCo$!HUSjF#va5i6U1E#{Xh8lfE#R(TN`}< z%UY{Q&EJ3z$5G2Eu(w}o)Jm&+J;8@)ZtzfEOL!Om$}MjTY99d1fEgtWb38xJJDr~= z!b0{)sIc^=Y^K+~|3~Ld*l9Qyzn}!0diWX(7q0-MB~rR=`K+hGT0BZ05juT$-?0S$ zB1ZWVL|US!ODK>9R$%L0pi=i`1mIRB6Vkxg!dn-I-rU$~r;$`*UAzFi^W| zx$tMny-ZT{=!WV~7iC5iCG({ktC)Wd_)cCJ32AWHW$nu(2}*$Np+a;-yg%a@Wg662 zUsEpoB088yPnPlOpH=(zvAVOzD#D@8iE4!|TT2X-Df+Die}?|1fdp`Pu05vQ^m6S; zvB*aTUoM@J%3+Xf=d6WxmeKp2XttxsK;@E$!dN*NGIe^LuUO0qZ%V@?-nX5E9s6t7 z*kfQw_anBKCzd1SW{>(hCtlXndiDEL>wUwWnb6WB1@!rS{3Tw^q7UE@D@uL1HuP-7 zgbAWbj%b=1IsP{uQ$}P@Ssg>3fF&uM8E6H82e~iGR~ryoAanxTK8UG4^6vqHuN0&Q zS4&}%L;o2<>APmyxGNr-3$fsg^Ih?oZ!9gj>Ydhy6xuXKFURxPcITrgW#Z;PIEwYR zmo#Z~4CtUzvy4I=H#>vuSaInSWehMZ#uIkgpYSPZjxtCJ*}}5l5}l8f%--(R9qQkc zfqsK``0`suA*~g->{{5;s9p4Jss-v_E=h>#6nbIdX=OX=I-teBF6S12TTyRQHPT1 zEuPE_zLa>dt&vM+adGBEP~Om8&8^S&j8nk5&h8_Ao_Blj1^uRmUnf5-HQhX{(zl_A z^cqmCU}@#RM*`bo+3Ri^Sd3S$-+v*z>@qfK+V{alZ;61+P&vKi*MDeDODjhsmNsQT zV6X-&sO5PW_(;11&ZV;FErgHcTi8~6Q~f)5ftQp%E9ET!RoSR6{)H@iZSugfEm{M3 z{rsL1Bm(?O^XXEI%UC#XJ(fIG#`Kd8%2H;4GxywjPcMh{JmD7&pYBcec?m>ScEu12 zVH;D~XA{Lu{A9${S;I$erY0%Ce|RT4!}v!mrWu#qr-_gsYlu$zhYOk%RjSRmsnjMr zv>A6^upo4bolt?oW*k4(ae^rBy*xq4q`tc*YHixJ!H~tM$_p`|lKRCM0?y9B8J*~E+uBiRgV81Q?huB9>f zM^RP~PxiUj^y%KCWOgA8y?~g7-4lT$CvsuXw?Ehfk-HBk2M4bIpbwh{Xr+==T7?s; zz6$ZXYReV<(gS9=QV>Bb(d*$tt8#<%4;q&L*+!Y#f<-<(%Mk6qLqvcH&i0fNN*|OQ zbhqyb9;jq2Ni?}X4jsbP;5z>>QbZN=NT<8_sOyz;&5W5&oYLk_xuk*yBFK71~C!JcH2DA4O zr#10=2Z^Ks8eKd5k+yzZnF;?YuogbUu{yv`G6E-DwLm9H-^@a@vZ1Y$5UJSV3c|+S zI6Gv|D~dC#FdDvj2_cWEyj<4cOWO;kKAmTkb!?wBASV{K`L{{Z-Q4KR4~9MP6AC-& z8bgY7in$+v2JwX8#1}8O#1P*GZNqV60{JbJTv9YI_fJ;+*oyRKuOC7pTW{&PXJ1J8 zA4q&Z!OKxk)!)EIcza93c17|%us=L6bpRvUoeV{pUyBmOU(pXzcrl-lfsC<1`x6gf&!i;|>*j^~Sp2BzM&g}8<{WDkF zl2xHavYozh(mqEWB$UK9o!MF7J;d|E<^BMN>(7AMa&v7wtxcaHh884e(ogwM5^F-y zXqnqL!hUKQp;6Tu$&-FuCdWV7k-=re`*wCBUlVvXT-GQeOw>;%y7@3TouNOO$Kq%b zv`Q-Q(zlhw*E}#4vm}DdT&LInc`l>+nX_*@W1;~2lVhx$KaXLtk0Hni(D{lgx|^D^ zKxL!m+aD_S#fIxIiv9#FlsSDipD&LLNjnRs`|tXAeku6U(L0mfsrP7j`ZM&)27^?v zSM&hGwir9|k;&TysSPFM39w0#GJ#xizIIEy1$>KwO~Pt1)6P?;w+RxAN*mZfU}I04{!?CX-fwhpk&&(xMyk1gh5l6bFtat&B>+V8=l=sWy7h zz(YZPyN}Zh)hw?(1L~Ysa_^4BuZ+H19;SY6 zFMS!@yyC@H6vL|X4D#&09K_IL_V-K-+u zEju1o@EkV6=lQ*lF>n$(cmGzC)lHB7ZVu{is=PQ{zZck*Ih#|E7v|YdxaUk?5Kjg# zh8MN5cJ2lG>U^nfoM~1csjx8fleIv}WBmdONbv-4U~!j}diPp6VG>T=H3bb%KG-z7 z@VBpMV3opfY`aFCX6R`q8oh2#J@{#{*r-k~ns6mtd}uVYvzfH(aB@PQnTdn5i@!*~?=Tb&dAl!_)xQE(3XB3D`&-pbb06%mDok z9U_gKXr6W*YPYw#8*lN|!FFuEd;6HLjE{EWO6S7jJ!f(o+AGC|CbC=3SItQ3ruQ|0 z5b~I(*+#N6K&vvqN;<|~0>7=uRTmNR5X$@D@g8KjETai7&cG=R@iP4iGX^IAK5Omb zaSy;C+0bG~5fOFTUO+AW3;o?_NyU0PH}Kk&?2C}4%eyH0oHHeWzG{G!9m23r;8^rM z#4k=6xL;kx?{e-9msA67TR!d}Umh&52o6H%`TZa1H}*RPxAf8hH42BbXua%F1M3CU z@1<$Gl>lL~9|R8T-4m_3tSSsC3E%AFa(&?2iaP!j`~TuhQr9ZrFYZRc{<(nZO7_E6 zaE)Ss7OiK!U^S|kifS$6Mwd0DvYyp4jDdWqAiuQ%R|Zl&GOo-)o&_CdbmGFZsKQe7 zpi0akmd%byvyn-3-TA5%H1o(|dTsn5eb4uWiEtBFW~3sJeT?;IxZ%@(6&|c0=f76J zp8D-k{EYf&=rFzAH4^b?F+k#H0s&+)nb^o8kRK*&B?geX9Upy&-`wmLSKFm0!gd}W zj;OXg*6%i0w<(YLMKx0;+{DtnQj~%c67IJtU%jg~jeQW;%ub&oThTr>IZ1;_s{D!w zmaRF*m+-0Vi~8?nY#{pT-NC{{l$`I~g^Ii>f~Sf!^d5Ni#w%dKPa;A$8TIrtUxMf8 z-;7yCLnA|Z9f#1#U+Md$S+$>?4HMzSS&flheTx0Le?}5vgSarFehP-5|HuHz;`tw< zVN?sJuZ!WE2$z$PjT1oJf)1_aYtoT)4kRnT4R~(WmLYZFm2Q+Dm1Qg&ojG zS^6!zCZ2BPdQOoX6HL+ZjI;A7GcZRDD#uuN!Q)ZZU`!&up9=SipR< zXntGlA$JqHO~Sjg*?8w~IjseBJni1?2e|AEJem;B%dVj9*(<2hMhtA0`9LqIfE8ws zESQEQ>wh)=A=+FWts8CmaIhOc;k2M1K`W^~*Ie+#giCNV>Px&0pBHitXVc$U2t4M- z2$1pV@|Za`;-NCQv-T9d9LcIVL46#?o{uV3e47?7f*Y9ih6Pf>sXlc;jPT(*$AM_{ zLz%tM^&k^+@!MdFhaFp`&V)2|3{#epY<-75vv(&_9Ac-8x0z(qCX}L7~IGw&{q5jDk8gMXR2ia5Sna* zS@{PVm1qiiIoO~a0>t-TQ;h~Sym7-k5W-a|8e?@}|3#z8H|IA0vsQpDUwM=_55+1_ z6{}FKN!rnI=uq0=MjP;gOZ`7*3yNz=DcD^$4NKoa!6vvo5g2k)r+8xX+nggrSH3V=>0Cnbyl?D*L4lT7K`=V z0zxx6Z8b!9l}p3Vy+z`RublzB2GVCIe&0J?elOF^FSfA3;`<)$40jE`$yA}*2iZb} zIYWX~?*mJ4e*X>9i-b{5-VM0clNvX z-Ga^HF2h;eDqE$+S;5OuPIOZ7XrB{K?k_`&5TIO#`!RYt!yHW1e+#5OMjVF?l8g9w zpD8?%hO*3vKx8lzunp?Kmqe>#uDw_{HNrSKbV8%sv`lDQ?e4C)1q7!0sRRGJ zYYvds9{6n^smv;qF+D`lvus2=_1xa}$&sRgJqvz(d-u67;IRpP0sYB z96>8fn1(XPO~Nv=Ng#Q0uQPe+6r@VyRAk(L<((UdJ?3@vFQM>59s%$i zCW1{8E?ode8_*SWtZD|F#WZknSNc^{R4k9j`20FtnJJm-w7a&jwR)G~n~$m0g6`a* znQPWCo0Tc@h9^8&g)?wvJo-Tsp~ZDVzh2!bQYn>^oD}$QIgtXfrjY8o28=t;>u7$h zPXBtyndmQ01{EbVY>)7rD^k*Tz*XY!aaPl}r@kA^Af}PJo=x%};I-1{A3{Ief#J)f z{U_LCf~+b-T$O7(liQM+g+EFE+iXeO@6jWqT@pOFHPj;!re)xp`(k%Z_6#{|-{yap zh>skl&*~`Y*xil=ipNLZ)Ra~~E_h;M%)0a22!tE{6?tHj3eoKYeiYz{dEyg)LSK>o zAQ+DVK9_!587EgLuzPPzGWc$A*N(Wq2=PP}1k@kv^uE>t228j=N6{m+$goY%z6 z0`J3va9vJ+G{9z}@7%b5SxBj$I2y&}WD9KVwOdhvi=#tbw$<-ek-sfTC{I@Zwde(4 zNv8cAt(-`j(%eu(EdGtvS%&C(+6c_jl9Xf?GG+cOT3LP?8U+--Wq1z@1rI0Uho5;N z`f+ElJ6V8$2*q!dTHlR>rs;Iw!z*}Lz+OqDXyH0opbq3D-vufO zb0C&N4z;20MRSLIBbrFKlV@JN+Bh%R&T=|b9|0)Vsd_Qq3! z{@x!Fx9(MV*q`K-&XiO!858l9?;77E1#}RyrC>?Nzrp(XurJz9GChuSb-WN7ewzw| z{sulkJ%`3W-0Dc_Scb;69$st;l-yH(f*ae7`6-mlKXX|_er-|;eEN0e45NQ(0z7os zkS!7b(&1OtzEsRBzE8tB;8da7#Dznw)djvZJI#g9F8|VTz4%*K^W&r6!-u{Bc?Pz% zKcs+Udu8%_fo`TZz%BSKHX9<2s0-TPWpg35d~(v2?XwZ~29PF1cYs^EL= zKA?I`2d=UJ_!wB=Bo=klmCH~mRsBRtu7^i(0arHbRdAY-KhYR9#!rw}{eMJ;8lXz9 z%ZjEbN-yibMibDjnHjU{A%GDmy(y9;vk^#sTjW@%l3p^|QvQ^h+V;sO{oMM(G+)!C zzXSXsA}i8Xw!q%<(@Lq|_~bh0BHlg;ZF&Dm0u_VuEY8Oj;vBPPTmj%sW|4~D!It0l zU6dg*6?rA$Zdz*K998BJ%M%VRcoET;WKSCF?bWXAy+B41xD#k@&m4RoD7Lafe294X zk$saCU+TIN@4O_vvz=n^AsE#VaH7s08e(V~jh~?Ge@vgrhMWIFI%Gu`CHM}RPX`;Z zn_kgHV&l^1cyEd%u}`U+>8oly8Ii#}`3G*`o3C77nj491cHqi$h(2s+NEc^Xy<$ zCi)CTj@(E4fn&-zj-%mc5cu)hm8k|1A1*N9MMlmx%9K`r4nE=dPIYq50|6uHfqFxP zCvfk%-hTL;KARY|G>ST12i_}3Tk#o9yX_q9!+oXBxHH7WS+WAbcdksaH9ww!Ta^N( z;{&?p*;p958SC3ENnz9qdRW7lH0)1d)q((Z!&Uv{%&Hw`yI^v{1LBL6`^*^r#$j%@ zD!3%$#pSCpK>pwLGNtibP+x6eahG$~n?vTsY44jUYl{ zZIWFI<4-Rlg_mh6V?^?0GGuC;!v@!R6|nH~cN@D{kDv7p=?g+olST zJ0*3V4-csMKwko={|P55{S_0SL8`wHp$@!?DNB`fqf4|O4Byl&?amo>a0eql0<&JJ zK>qT7IQLV~t6$KY(&)RkU{f+%$l#w_+xyOAzto19vrm9d(X>Y~mUNCX6C$ZPdmQsZ zfQnouBaldKS2GU_FA0{3XZsb1Z^86b#Spswea77>gsc%FgjW(KBtjGv>l%u(JAG?i z`afGN6G=M}0+cxN7%(F-)koa^!`G46(MBOj9w@?6z)0A^Ne3#uE^(JO)^iEp*!IuN zn(MlSnI$>LqJ?Pfm{NO7CaG@jlZQ>_CT63?PoHIoxs5}F?Wig$Vh^0tf0%GKu>utv z_i5>$5t7$58&C38>fE~Akn4d`Klg~jXVd_JeTOmyLWkG-3F0ypXnE;U%0h>@0Qm}P z_pY~Wux!i|zIb-HVvG??c;z0ybx?|C>;d@X*|Md7(E7m8+iN|9l;nQ7ORs7D3lo6rnUQ?lo=iu*vTr+O6m&d)mIriS#z~tnr zN;aVgT}~HXYQvVkye%8Fg%C?ghOXS*Ve^W$wy-_|8Hm>>v2?(SbvuEkG15#5KX2UE zuSbs9e{b$BE35(VDpI6Cw7-HAV!#Q7{g8pKf2yYgP6Hok-hLt-u-BjR@sYK;VQ*g< zIZLWM{%$MNma+|T_cz_c5ePOUCwcve9QJp$_0`oy?jJFW7G9rnl6zjgzvT;p{s5o$%Hj=MUc*HLP2d{{wR1hCJgk8kytmYS5j=Q9&s!i(9%BEkbQFW=sO2(& znr48Qqz}3bv?xO;K@t}`xSU)SIVCl5pK%K9?6sHB@T6WVT5iWb4|o$N$nzg+Seo?W ziXVEuGXavp*X~}&WmWLzV=0+;p%>dZZU`!eZ5c1`zT|yf#8wpTWeHB1SQJ_{rVCB$<9gRec4fZif1uj_1@2*vZ>WM~@B zPDYxiXB~Sy+RClWS+aBlh+qRzT-24!W77Dq5ky2j5}R+*qXw;e-xj&t(v)r9I~sfb z3pg|Azvg^7A>!={+uFo|rRiRw4xU%OW9&A$5AYUtt{LxpSi2Ju<<)ad{Mt)x`g4Q@ zpi|_IW8~EsfrGyYkrN4fE(fC7T%UgjaOSel*fAg2R506i3yGm7?C{DEt8J)O&E3Hg z-#n7Qs}Ebk%{sEsb)!BF-GbF{QK|g~#tJ)LIf}~gWSF#W z*XVimu!2JB8j|_$TlnvK4Pf&6aN{(HiY;KOojv+Y^}Xr_20NJEYG;(0Cs^^(Fq_4P z!Wj!UaiUanF(heNswS@4dudsd5WI`5#g)nC$1$@4b;tm(FEuGh=?!zk%Ydn^Lazq6feqaQ?G2z!$PpMFx1 zO=Syb1gVy#iciKX3GEuuSHHt$01(muL_#G9t*eYi=LteS<^MuY9HYT;*|deDUE})| z<76u+=8b!|a_8)!RvejpX;PRC=>2AqrD9&MJ0y+wSdVN!y4By^bR?M%qgn&O=HTuA zOi3lxDfY4Gle9mEHSgp3^ZyuNV2B~>m|7I4f{gCDv{VyZhVE%bw+fR&9r+swWZqTRQT5rn=T)kl_PppFb?v6^lqVvnTE_{b1BNaBIUG@0nDQD~nhpY@NJ_o?K>}7@&4QVt6hot>)S`S%Fq2ux1>|##X9Jh$69C_pc+84|ucH z!JC4>--iYX6c$q50nP$SMKsU`)l)KTcC@@e631ucL=r;x_v|~a+B8Bt88Pk!-tgpZ zZ6ZU|fqEV|EA9;e>Bz(yp6$NHnaS2QOVTIf9eS*7rVUs7w8rX~AbWDz$giF$0cg9* z?#1JUzRrFS%g=n}AUnJV1U-FsI|}hl9XKF3*te<1neXZDr&t<)hy2J-8lY)Cx$9oE zpu^kz4i~`;ESuedq>vrbw^JoLRPESht%pU+U+hKR5OC$>(O>NRa3S=W_7Kd5mZHix z=(Cw~2;zq=(!l{DWGsAn$FM+=SdYbYrh(HQ zYw=$WkdtnGL0^cFK-?v7rTA}Jf60oq=LuToxYg%VmTi;+n)MHe$AZwo;)l)k2foI_ zRy!~a)6IXTyrUg*d+!-#;vpR1^!nWb8ZRS|ub(R&dFf=SBE;`jN>0p2W!Xlgd-=gc z+=r9^(>}Hv{O8_eQ#mixsHTOi54pqgg66vkc>-iivt)Yw+)zIGOhYkGSE_gx9LPv~BXXgieU?OOQOsj~q5S1(v#Qayq<8Z@=-0uXtEN z?k&C+5c}9NMWjKfHu+NGYg6A#X-`FK^e5}=DkYr4!74#eyA438^Vdegnf}UtzgzZw zror%~AS1r&Z3;gA;L_;V72m&=UX)MCPE^ZyXO8{t!_N?BiMy9n5!plD)j=4Guc{Oc z5p3G*CpY5*qRS2DezqX|m@=P3h{5bl^Ju+%gEuD|2gDaO?MsSt+bU!C3FB&I5BK70 zK!h=O1b-10`_Gy}y`XC+yRNGCZL8k`x?P|R$;toY=q%i#e7-)syDTBy-BL<-x02HN zr4gl(?${+HL|SR3Te>6{q(r)#Md|Klci;WJf5LM;GuN3p=l2XkKB>7nx=o_ zs<`~t0%D?wtS2j);rg7&tc$)x?|x;n8B(#L%w+|dQ1s?$J0OVUL;mf+k;gE9+`;vr z+wNUo4`(~mgtD#oWs9gO1G_7Rf5n&Tdpey@Do6059XJ=_dd7dU9$-BR_)qU{u*yHb zwqveOM%Vki(rAaMocw9}W!!Zzea?z;&Usj72iZ9$4p&z=7`^RZvT2d(UV0-o1CA=Z ziG$(g1Rk1z;ouC$?#M!H3vmS!8sIT40y~`seK_oqiAhOON3UknS64@Lk&_BJQ&0a^ z-1D#T+zb>hG~}-NRew$=(^i`(&_<~PMm}-vK4Qz_M&-I0viwAE;%h)yqa&!i6IEJJ zFuazBI5P3r8LeBr6=p&?Sx{%C*y$IPQjto?7aFnlX`WsRni8y7N6INMXIxp^zHKkX zQV0LLgjN9arD*NTtPPY27!CapWV&{@-Jw84e8SaA*bc_U(-SZYF<}H zmPTU%s#d8$0m>L>KJC3^Pd?uPI(V(Ckh`dL`qwznQIJ&2cb!0xz~zHoJx)!V7ZGr% zPxAgqfxm=h(SG1*l~g5qXueZCKQPfMK^)#jbo$c;wv_`&9NmwG+@A>Ft(xR1m@DHG zH0z}SOr>chmXg}<3KI&6sysFv#9jhtL3fE_7+zHT+OFmnpIhlAy-4#=GLOKi>GjVG zsMgjqR#FaNE<^Ufk^VFXAaH64Sk4fxf8r91WH7)3&@(PIP@Rc$Pr{CN5KZ|pm?3Mg zK{@|LmV^^oj=&cHQHbvqUEr_>*ZZ4XBgllIH7LRo$d#WQj3*GzV%Y%1%b#M71ljMH zoEe4ndjhVDdW{&o2KdDSj8meB? zS}EL$V3%_23d#v$`s&gECmk(d&L(Al=zbE;O%c=Qn|iZeo^tPQ>>7?MIB066e16?W zeZcfOAZXf3`0Vfxe!!T@I{lRna221qwiv1S9xJN+;?$1m(R`ij-%f|S(n=z)saay5 zV=>a8vYcYw%f?2RK9ub|knt?8i(fP55{soUbxKklw7j|!y;8H%w`5mHp%#4H;hGR(Z13#)Lp6;x z(nA95PEY{VE4i6?!~bI_BLAw((6IOk)SBPct+CFYXG3p6NXH6`@IrUieS;K z!ShQ4keDW3oy>+xZxVVJA7_0$*detc*8drVqNHDL&60Cq$Bv`QkUmv<2gn;DwN-3y zs$eW9tl_`Y!@5h{ZLpdbZ2FIR;c6Aoofrfaja(1D2KIb2~xra$-1IUkZV-F(>JIC-*M}27#kc8cQX5=&i3Jja8Eb3 z{`pW*OD}T=_SF60AocoryE*u7{S_c9X9~muv{beMwRcdKCk049MfdMm!*?>7i?Z1F zwSwV)z~pr+E%o2lg({>uFh@DKbp5vPhIMl&_}}F^ zZT1n~{x_u3UTg7O6t!3Zl)2and|;3rWzo5YMchHy!8T+X?BE{Z9Kg$Gz-2HqUh3ZK z4|nfO!x3fh0NxlB{d0zIek6sG_ZZ(LME_WeN{c4-?@>%blpvP{q5 z!Kaqz!6$9=wY?Lv#@6xta0sn|cNMFrlPuDXs+iGc%pMSU4!@5!|110!K^~F)-P|zs zK!h^gm`&uTw@U_Vyt2$K9qF1Gm*E>MjHex6niV@jvnpHh;>(@^OlFgSu-=IxPuKpt zYsv7~lsgRJze4~R%k{OVmCXLpQ{O(e4i<5$;L zHE^=hrmh(X6u$jB2S#33#SA<3=fU2d+OLN7$+6(N8}y8y-*AMdJ7Zfm6CS0HVPM3I z$T9WAyjauRxxm=*mCde%AtcG}mT$yMP)EOTk%#6Xb0CcGC=kXw-Ji$n@xMc#XuKun}jxi^9NW9zs3nZSdX8mIok?UWVx^TpgV!)%E z$SVfd4_AR7<^}5j?=B9BLIQrQx&T+(KVWpu2C@()Ngm(l^+G}}Nj|m7OV@=@ucLg8 zQDcw%!@xbjH_KyszD@}NC8dKl6+jfAe9_U?o-oT$LvC`Ox)0kLA}S5+1IkxcDP344 zX!F(^oULZD%<_$8-2<+s@=U;I6$$Eqb|kD@E_$N;YvQl|phTjAO0GuU>h_22(>!qV zCRJ%&_UQ8a>JU_o+uF~A4Kcs`Sf3q2&nNX&I4dZdNnW;m+HhjCh3@9<=?!Dgmn(ox z;6u0rXZS%fk+eA?WXv@%qvw?MD{mO`9w@i*%#TRFPWb6hYQ(CY)$=5j33<4-Hh<>E z4NsA!`!(##<_E+P#Af%Tn*Oz!mDRxBq{ONQ15^osj-J+HOPHk&Jzde`-phc3XdY$F z%{(W3SMJsdu7RU-6-;44#MX$Dw6lp(<}VYOs8X0Q_{;#^jOZrV;LCmU-zPW z3J1{7;Yk%=RjcxCtWMH1%8FJdYSA%uYAs%y9G7mlxxSbf%01yOfvs<3uzeoVw)NJL z4(iDM`|Zx|HQf&!Kn`;l6S|Qd1WxQqC3@J!Q+s!{+yANpim%aW@n9I29VWXRWfRF@ zT1CUW4q5(tJM8eFWn*&kRAqsH{kVac=wLe^I^V%HOM27DX14XAvdyWntk8BW4giDS zn5MH>8LBeu68XXPv|Ke*D~f(9HCF|7411c!$dp)f=LE}E3V~|Tby@(&hHR{=^4DEF z?2aqjobEkb@}OyN$QrRg7I4(hWdp6RsK;ux=wOZ&%-KEB2nS@ z&ac1nIY_%rCo_{X$md!z4wvf;TaUrZrWJ^ZEcxORGS5o({m%x$laPM=Zu%GgZDJ9X zV=zxG&0?`aoS609EmOlY1vds)6caj(kqtOFa2du=hCE(v@$BqSYLuha!5f#4dT@&* z8-Ze=&Mw~S8fb##3gdRYlgy34=!|BH?Epg=J+g#D$fn+qs+jA8bzy&THY%&aUQ*+J zOx?MF^F!{9?&ho^F@G(0YTg$1-Cboci7G~#tOrui(m8)lkPX5A@ZXMRZdmCW|A2x@ zI+LrLLUG6bBH3BCC&YxXzDP7G`X$qmSc^qZ@c~ERk{-Tv(BCltyOp8&!wO-q{*An= zO;It84P)LhQy&qbJcc0$VqxwP_Tc~$2hNAeLyx7!<88byF+V40*}e~KP90NtS378G zNl;l?boIXUDkG5lRKE!?tZs|SyCHdG6V`pEg>RkPl#VwcE_XGV$M4Do`gy;Tx0X8b z_*hM<`h>_pVlQXEwU2m~x=$Z=*$96gldrZW-nC;d1oY`8+7QPL*~{E51on&o%T>RW zUy;SRX92U>EaYOoM+2k|L3Jx3g;3;DCvo&MA8vQWYzsl)JJFSvmT)3>)%?KkDw-D= z^MKCSe{h&5SlC-r>~2I8^~V6RV2Q}Mr`<&5w7M0tkG>LkKTU&wL)2Hz161Sq>xkp) z>+6yGmYu^X--&JWg;mehIJdZ+jhF1n`Om%{UKL3?CSuk#v+t$At$X%gESp=+h6cY7lohwJu3Va~dL@>f z3D_N7ZmIydH z{Sr_9U%AT5O1|sF%?$;Yj=;Vg@OZqF` z;XYR`a%n&jd@80(_|uP5>0D@L0I>5c?7)9!_S*t9gKH@}Gu{(sD|D8y!^lI8M^d#abDMklH)-t3#@( zD=M6eZ92Vd4A;qk;BT6*F&(2gfnPwVMi;>OJ6h|fB&KOfa~CYsg9d7(3fX~W+7T?h zZ~A@g?l+k9yg&O0a?cUp`;)X@H`$Ih`=H9mv<}nc^x&48d8>kn{>`dBTHDtM?2I4;wBN@k?5SwwSe`Uq}ZoN~AH= zqbUX6w%FciRo|>|!1B0Syq~T_J*cBTeS;iUBXf$;Vx5AnMy*N?vN&*x!s{ED7TC)* zBw7eabh4*l?SQdNS9x9a_w9RE+=^|Z&q))17X>{FLe*b`*4ujDK$D*2dqTNCw;Uf} zIKu1Ib3fd!)-fy`?Z4v4;B{O=9l?&pji+tJIB_eMBD*`RGNfXwXNABG6O*vS=D-LvjNQ3UEK zkLY50Z83QoH9Z13q&t|Jo#rA*nffbO95}FXaR6}wCBr}|y2Oty%`COU>$|W!v<4l{{ZAfb&-`Aj{W^SVsleCd}l1id`O$tY}aq;~PjZLncg7fm(-fL99MSp=rF$!u(^| zgxF}%6W?IjZk?3q5dT<(L1bo*ITnJUf|#^C zNw1)Pu4z4oyuiczDPZG@M}G+fnh$~$v&#fN$xLVAGu>V{**@+NcHzV-0t8ef9hMG$ z;qalTgQW_e;DU4rf-=tOtt;W44eHcI64DLtL}iS-AC}V=av78k1< zzsb8*!5d}(K_L1Ddy8Cs&_wx~ptL;1#l#D03bb3JK>6G}`8w0jhJFT1+IIB>(T>=sJAK$AJ7Yf10Z&YST$`#!! zkbarkJp388r;hRYQb(=qLfSf|$!fc=A98Xc)vCLSG3okk_Cf4tr&$ym`!v=jG5P+P zXIV4Km45$tRz*K@aZfx{Sv**aU0a{%{pY&3KFeD={byQer*wCI09oc@WTc2r@@}~e zt3UXQE}Z3Lnpq644hWw4+3o+B21NsGj7Sv5RD^*$0Fhsgwfc{MCij!Uw#(;D$()+Y z+jIEQXl5GE9mcqDdN@_k3yMs@<>H{6kr3?cUj|8T4RP!W2#H~GfV-?W)-7`;4Vi?W zK%WXV&3)VsjvZG&C{H3P`oaqHSnIfZef z@6VIH@hU$**e-V;P9KFyJ#MlhxGfMdW~w@zN(FVUN*ZPgeC}VAgJVz{%P5g(iVYV# zlBQWzJp+bl<_3T9A79F^Pv`AieeiWV`RH<_&?F!oGq?S9 zw-`BdD>~~*LhOJo#sw;`CA>uV8reoE$rU^GP0nB_0U8@Mi{mO7;~3f~Wgx&0#iE zxrZ6J{N`e#dyMvsF-`&MP@sLD-})jVJC@1*`Pb85hPcCTde_1Ii>mNcWU86@6JZ~D z-7HiCFXAOG_JQZI(=VAnUN-Q%6T8fB)`C5#)#I@Ii(H7!w_?clxctNcEIm=|RUwI; zWj|<#(;-EJgIgapJZx#+7oqa!Bb^KD7I^n?s*dSzKGXhJiBj!`n}OxyX?^;pJS<{Y zDX*=hU2!mQ(hM3O{{h!HcxIv#W6fORGVb{HzAZ5KByXb`7ztdz%@dF8JY230xYm+h zq_T-{w3VzdCXq^Ttzic1nj5^vi^kcwb{s#xuyDGbQ}mgQv1zUUD6+HJmO5>vvy|rBz%pAwOK3QlBAl)1qR}Tti zO;HlcD)`nU{lA1KhZqQ``@-YvOU%SRXLqBVa342gn`_0?ke!Sf3BMgyN-7f)+_QN& z4pwNugsh1TeBRgq@y|1eCDEO+fJe0MnZhB~S^Byk10dxy%Qb@wne5A7i>rlmf<@KE z7u4CBP8*Xp;Z{jCo|bAFJI*|ra{(eX_0+LuuU%$Wdc=2j+JrRYQoIl2D{thjf9Gt# zL^@zfb8v%7jKdcQ?)9h0^_+37H5oy&@^nf{5^J*;708*Fz0J*f=|~UEr$S)z6(?Fx zSAUADz?agG2AYb?c{4_4w9TA(t{7z>mQn~50j+RkoPv~W&c`XWe=xl)fUJ4P-;4t} z=9CvUqFI}v>nbXo6*8%!Xw4ikQ^?xaJ;$0UE7KHb>TgzqM0xaEr=L}nT(QV;1DCK| z9_=sIAJ{jE>qbH>eZ*fA8{K9mTo7ZG8Z?P!bxpvMlYw4=Ecjoo$A^6d?zSzK>@}9B z=?uOk;hxpq5dXbp49LE5E+>A!?Vc{Ylj$L*MSjXNm&j59;DHIxq#N;;doOn3BLG=63t!OF$+0(|3uOi_2rtn>_@({XbpYPQHBb`I3@*)x<6$E1EYY5eG zH3rBs^Rh0Jk3L<>-n9Jmw(+;ZPOrzGeVy=}mr zl$S~(!0O9t@Acw)IY5>wd-C`XdvIVm4>P);(C;}5^+5n~Tkv~GL@ zWWg|R&)~=TXCKjh@c?a>1Q%ogQ4{VAg9pxkpeH4fFrhh^&_4gdpCzP=2XepnvT^LV z8Yi0iM`q49a*ELuY^e zo!k3g2+8?5Ne?Sj3V{E?Y;Cv3zZ=Wk-;D&DaY(iiJ%wy2zM*5wkL%$*8;qOEk2JAp zjtj-G_kkk)AO7)RaZ>Jwv4pRmPzP4SjA23F!?&xn>%z`&?k}YS_9f&o- zJyH&2k(eQoIDfpLycBMewW1v(GwX|T3pc73fe;++p!ale3OzohZ0I7cf8#Ojoe8XQXASXLf1>d#H>TnC#fE3t)Ed4=%8wzOni7joG+%lg+=PYDWYwJ z1+i@&P>@aAbtHaWa{s!RLX9r_@Lra8%X`DJ&O4B?FNK+&AdFure@w}7fmuj%_`f}j z4x}uU|o)=rw0`SiJ$4-m9$I!S%&U! zx2oqegY&x(B1%2{MTo$T-mod|rBDaA6o8IfkDSit(?8tq%ScXc5M?CDl*blTX?i+ch1ICC~FFGe;P6~b9M?acFS)p!AcM{I=gS80a8^NTvAKR z#?H;LefHBJ=~+)eN6LY@3onf^ zva#}+Y6jCglnOP=M}{yLREBe&npxITY?Le>l+Svk9T*8BW$1H}V( zZZfFL3d*aqxg(AWY)jtrp4Z0d9qn$OoO{kEqL-kbzD3p8XOcs`kMTG12`fZLJ!7?P zX{jF6KNKx@<-5L6ZeY6!Llyb$p|qiI34O#=wrvew{ne+H_4vfa^MfsA z5>JaxZ6?e{&+bN65bw#t%8mFCnu7@L3?v256Uz|csY3;p`dQTFQC-KJZ0KDphJ9|sfIGufvb;M;)wCa1w~hTU^8 z66yt(=~xeMhzbYP^JIh-tH{0WEsSHKyX}XUBD4$6&Xpb>r<5Sr9hRB_@opYEY5d5} zXGv_>E_0VkiJvkNtO|}cWC~;Kg@sw6d^30l!L6IjNj4i)HieREY@?#x@% zOoyM+w(XB`zF*FeDx8f-x;Geill(8P?}@ZPVyBvt$6wCr$2<8G7Z_)b z5mrgj#*_L~J2<^T%>{An_*#6d=HP&IRbZDoh$bGtro)*k2lpfE-A8Ow{4zYs6nztXN?RNmkl0N$tlg~DyNq8 zHMNz#C2E`3)fS>s$kgHy{HQ^lN~3W3?x{k6yB9qi7Txy-u=+fOdHw0{tq@HnTV3jJ z7IP2LvjN3D3fiV7Y;4N}JwsKHX>Gb56mm41iFqNDg~@u(o1iebej;||(=4>uM)m4n zm0pj#6e!#)5XeETL5aE4Q)^?bc(&Kpp$o*6xwvzBqdiWi&;>p-alIWme~>`(MP8MC zi_W2~+;2aAvP;Xaj_X3b0u>_D_??{XN&1q$E6ADDu{@?ZJD{m%5rPekuMjKO8{WTEljNfSuzzU1`^3KfEA~12|_9rU!D9oMN z#Ol1YX4+cLge;zw_E~@wiVFqPwydqdt&nsl8DCK4q)Jxj5WM^SVsXs#tl@v*JUG@R zFr-P4m}@jkTY=X4rp2V7x{3h!{4%Kl=rktl2|k$00W>|-4rV(7*0hXFPyIZV1nUoI z%%_8)c|n8BG0KmvEXI}t8~%TtNj;c3dK*ch{-d2&nlJOY^C%qi_yfn~O1mx^{!oO{ zpT7#~>ak8)Bl@v+=*=tv!St>sD>Bu-`>=;H8=&k z=r^)kzg<5h7wXyPMih2f4f|Q%1eHnydAX=3v(pL92mj_- zG;w1;1&Lf@I_8F11bXMv%o zJBQH8=J8#{Y1>zFCcHcJ9kA4ZSh0_4k{qwd7x4_bKrbOHfuZ*(3f$sxI^D+e0QzA` z$;kt)=p&-}6_Ui>%KXl^7&T8E`H0mOP9(dW&)?iBpYxy=yHoazNw7QHV)P00b>)7`nv_`wOqy=Nw`cxIwG%`6_Z2#-L@52S#dRJ6L=RA zc8+a2Mz;6$bcl0-Usmg9|HZ#1?wS3@NUoOBL8&8n$5*n>JwCHP&K{1Wp`i>vNXR=g zScO|(ZoaBY<9tN7#>k$&1|k;V=dJep)@1R_w1mr*@?y;km)Vqw+NSYfL@u_6pGO4c zdBZ^*&(Ex$0(I>j1&i!{ zXH!^c^_{K5s{ejG+(>3>rj=FM)w(s2vv4Id;yp2Oyf~e3Y~JApf{w~)e=%^9J;a#* zSx`QD?9KiLKt0r?II=MbmXgk{_kQH4n7vvK>lC?rUnAc4QvuI5pbAOIK)w)=ABSo+#G&}yZq8ZBSL-JKyO?_$U!4inp-yni-X2T%&4H| zWvs9Bmjg|1f@eQpf7*ThuW?4d2}60=;ezs+DQ&p&dG1P0;REQD3%#xys{6VS!1oLh zp!sR^IIaVaLO{ABwzM?pk`d!-8(oWgAHuvyze{?jQ1yGzdomwTwSw=qzq~fZYNIF? zE6bnIEN}6egWn-59b+#Zv{v%q@Gf(+l9$bqVL;;60 z@ak|_taTHA3H$tre@A}+@~~4j0d{UmefNXyV-y(Ii6*_hwpX0E4m5wr@s>VJT0eYP zj^#zv7qw=kBZQ30v4S5tOVhdLa(juo4k!Qg%j|qnw`qPSv1$>FLgf8+GX!~9Aup`& zKQ-W4+wEZZxb626)OJ^!Qo0s{%l2QOjmBk|@xwORAZIpJW=SwV;uQT(*O9l+eo zqIV#^C`eY70{{TC%3+Bs%H5$g0Eq<;7{tDC5!JgdKQ?u3CYAMn;(5G(we&;Y>**dK ze3EoCNZsNi+EZpk4MVO>Smo+ddt5PbabwxuJW(4vWY{!g8-h2)vR&0c`8N$2hy!=Y z#K#T}Alj~drm>BkuWt*}Q3F&}(3hvuAR1 z?+m=Nn}M_0p!&VOGpBp(@6-z^#y?R{xLCsh8W1WGT$-PO*@xPXz@RPawEvWB<%5W{ zCgiK7d>tJ)UFx6QC%<_iCWy2P+vMyuAjr{0vfbp7ogerLx3}n?#psZj2yoSV!hJnm z+u=l93qX4sFd|#-?j)ig913P-1+LuOzQjqLAo09v?$c*(g zy#&D)ujVl%LLWe~c33b#p30K(x?r*FH~zFUJ4azwgfCyT3#o$Mf#!o4T&Ei+bo7;O zlccWoM14r(za&vKO1z@Sv?*d8_h=Bc`Iqx?Nk_M+{RP6i0$(Ms9nf(fa#F<4b3H^k5(wU$StyruG!9)DD%(nqw}4?T4^OdCPpY6!f;sPP(L$tsen5H}mql zLD200+P}od8e1`VBQK0R1SMgghO`rHMYF;IFs`NeB1a&mFgX{;tHEi|o{VS^Avwk3 z{K9a*aiF4Z;AF5nmsxLj1So&{zX*XO&13oU-c6hk%lgyazkO_cgr8%5ZUuV=$m?1H z(vkr-j5+U^v$Yfz#ur{)iBB5Ax<6v@ebvP7DVS z6*GOAnO3h%To_;zrB_K4Ja1bdtDCz-Vl#d=blHI4L>{}}U?HaJ?T6?R>tfDd6S`fk z;>(0V8y|;{VpdcZ###20x86+bo~K@}e#e~JIqbt&@7QC-^$J?|GxgWv5$;YpX~@LM z7_Z}xINB;;hZ5GX4`EU+b;(=|%~c?qGG9)_RXze%1 zfSHL(v^#vC-}?H|7)w_ip+ZHLCcz5FIe(P9b*3!*DI_r2L}A#&jZ0hFCnN>U%yBzoWjIZ|8#wQNFc1)_)X zpN5XQnmc-zXOg7h!9gCBs^4jBv2BgnI>m@y9k(jSgt(@Y;z=BY7G{)Y7YlAJwKVi>N78Vz#$)7 zvZ=>98CuTMub&M6LYN@Hob)wdC0MwmZIw5-W+(cU8-qX;{MVKT! z{GDp-+%(ei#YsS)T<_T})Hcf-P?v3;1T*q_G*G6s4KQ;UG-rDMN;w?h0#E{pcb{xK z?mG*YNFk`f>keZ`VK+!6cy;|hm)i5Bw7&R%5@>$u>lg*TsHuXMY^^#}PSZB0%Fo}k zAX>i$}$Y;XYg47_qR$^@lE6nlh20ZOFvi4)&9T9w1Iv754q)(Fh= zkVE=bkCEjaA`S@&qCl&=86etExs{0n?Y?Z6mHl$enS#qik9<6p4IZ7|M%fK;IOmEk{6gr6|bNQl2=&Lwd+Z1l@h<*K{-zV z3#yG}GeAnXXKmkfo|v(~%9avok@qv(zuVgqsTCsSl7+ZA-{BGDyIYqcl+->Z#|N*h zRb~6lU3o6GQc-{H`U0-D%CoMNcX?}JuW_y#egrxYGm{e7s;1y?Z{%-J`{V5s1O}gREq%V&&E5C121oZNIhd z?5A`Uhr05UdNX}@?C-04V4HL-R3iwra`m!z^)QFdW5wK>w?Hm0{pQ?|vf@qe%>6F+ zJkSu+^Kf14ktuRB5>Ktz`z^or*yvPJMM?BM>tue_6@CYQEl~U2#Z=-aD~2fmTMicViP&6Qq)F&DaCjF=<-50q|M`prie;uCa>H{`rQyr!Ape zHJ`okf1{(6W55})FB!dTK>HxZ%WQD$`E!=eqwKLg$4WtK(zd*l(7i?CZhs%>bZo+f z&4_ruN;^uQc5d2WuTGT&xbbC8DmO;cLSU;+0&J`1Cr>{GqR%Tw2xVsjzokt$D-c=I zPjv&@)zOR+pe>$a!}Oz4keT86z$5Bt8vZ`c9cn${joEM+d33)Em(6g1PZK4J)cvHVs>?wm_ON0U zT8q=;TmwHn4c;q|X9!ay0W{})c)&5jS}&5~?3pxO(T!YzpG(!y-NrNZ*b1Ycxw>3M z1GvjLR$=8cgZPXRg0D4$;t}$qqC@K@kJj!VB2pIk71w=m8^~>J=sGY-;!QT2TaSvh z7+$|7XPvyIN9)cZkG)PjwWY5x$`u|=`U&d~oR8MlRO8*g{2(Is$XV#LLs1kzGvA5m zzs3Av({t&Vg%PqYBZjuEK#jJhQq6oFRysQE#e`P4ohp+W~VkN5nit1FpA2_@#=<9{EAMhs~N%UeLf;VRE0ru!4 zU<#~lxJ4C|BdJ8gbJjz|Pdoo8EHK9$PKNL`G~JN zAZSX`!##=Pcd|E7`rYp|K`^kXB$VChS82Po6uugj=>b(qqfRVTb#E$M3pYgGT0j&V z8Xh}buO2i$*wxP1BZT>eLMMA%A}Zs`0iV(>bOxU*JN)0>CJZA{+2u|U4THG#mflE7 z7!OWm^}dktdt&uEBKjlzEIBW~s4cxO@_#E_m>RP4cKZN@X6O%V9@VPh6ZQ(lIANkg zE!=2iSIgwr5OWM0VzkztTH%OJKnyQ*rP^EqV-h@IfbgdN{@=$U+5UYWthBU$3PRbN zdWpC*&$x=Py5|8=!~yGuR0HNGtMOV4*Q}x!fgC`$=j{O(s}gdkvCXDmx*vtYj*mCO z-5i~q%gg1UyN9|=0O}2Yk-dU&Gd2uh3s$%1+83nU2#Z?YbGo1)ufbSEq@2Sx8P45( zuK1m-NJe3Tdp6KZn^nn2`%h{dz>z&K)ZF|pHH*s6p3x`VZtpFTZ9##v#%zF^V+w-k zh5UFV$B2dmZ{k>=GiDx$id8?0-@F!1d(5TiExxnH*xZ@`Q1 z6hll2mI-qKYu8<+O?H!^$Mv@FylXlGppkLIG{puqX9etRN7>h%6`0iBfCivXD}w`mLi@*)|BA0J2d27URaZSPIabYI9=s+zjTO2}d{aRJ^M-6bv5vj32ib{hrhh z_)*oe48AXAR^PvRdH1bXgY~1ktPkn%yJ+?J3*>#Mo}H!EmzMIfQ?faxk22_cE^Eec zuk$VUDT+ZPu$QzaK|E~FYQYzo2A!CLhH-!_0vCAqmegk+b@ww+wkuc)gSfmjf8Gpq_mpyob+j4ide#SqEwK~5$_@2#T}IR( z2`=AYS@e7FHm~@cC*f6t&Qh)5QX#nUnx4GW7x*EG0Q5r~Vgq}Zq+(Hc#gxVZp|-1d z+42PqDC9Zj8vr@M-7DsA-x<8<%hEPczjJK3$xr4b?IQa~x zkZYDdIAmuzNN_efBDARq4~!OPs<7DgA2a{Yy!|+xdq3pK`5D|dH2^v+Bk*& zr#s4_orz2$`-+eWBE#&{yDQQd%**Iw9Xnr%C{y2LhTc*GkDFiAq%qw+l;PVK;NZA_g5I&essI=K1Gq zR>?vYLIRzh)i3an6j72`sZ8jiYx!ib{Rci~91_mBb8^cGY3u9*G$*%ioVINJMeQKoihK5&R} zTPpbtM~Z1&4vN6w8hhf65Y%=DXdf^mOqLw}fdc7BpdI@+yKBv{B8i3kqR+x=Z!eA| zmo7W?a}RwC#9ROQ7%G^CY)8)0HnItM&W**t+z!bPO!&G>W_Z?8vb<>xF2(z$p zOJtWO1GLaDehh83#)dsN1CaGLfc#~?^>u3z{6WIm82xF72a&r>gn;xdlG6rS2m77+ zpFiJL{)VcW)!hN{V{~h{2yiD4V2XfMEmDu(xiCHll7Er49k|%7t#R(?`1yh1)zz^o z{{~&mgx-Mdz0S2=cSC^Q{V!lp9I6 zOR6|#`m3TADG*1Lg_TR(f1^YbZ=K$*(EJ}nFgxtP*2=rQ~#7u@yi6j)1jE$ z+{r=fSGYOdfGHeD5mqp|)9!NX^A}Mo8+B8f8S=e*Viqg?pOYW?`t9B8N?ubkqDiE8 z4B^QaeN$oF4fL-6X`}XHm)U|K;4rIkHsccXq1(il1!G2WFA}iAhK1-_&PY=cV%%+W zlJm$QW)+unh+wj+0!%sD`(|rfKYwVnGY}T@7mIa|cw_vt$$M6aG2kCa(@zhvFZ){v z=;8jO>K{a25}c(w+x$i>a;l7<)kPY$L2tAHOSk7|1>LoE0O8_JA5wcx=xTo5xDfr; z?BgeQr%xRWwrXJ*YbH^CjefIJI>7;J_dZ74NXzs;dvA?TqoX{ax;`_)gn3O{NsLS5 zXl8Z76{`Hx^Pqn6_7F7wbT*%HynUsIl)L|+F4Ox<0~an6!YCi&94q?FeXsG$z*mza zB@tSzy3*=C?jJ;4Z8ouLuJg0_6Y@E(B2sd~_>LqI-9#4*VxA+b^d{-)l0KaF-|ru# z>;)$6wWYUQ%8*CJC6TMVqAeEOtEM(G$I4(^SK3obc8;{Af5>Q?3n z_m;P*VJgJW(QKX5Zok&Qyaa~4A9w|rr7yvx$F3v73N}zXOT6u(>D7&eKIbRR0-c5~ z5&oRSuRi`+D<>5d_mS-t0z1JznkEUqAv|2~4}O#Df9m*p&+*UVO-x4#hVh6u@SaWj z8juFIEFTE02QC1O%e^ig+gz9wNrQ95ECWE)=7>6(McId;4B3RPfr7 z2xfM4;38-mg$jN;S%qB57?Y~c-OyQF)m%ov1WL z9`oz(j$6~kY5ZrvZ<@Qmw;gu60|@8K%G49 zI#!3(%a6Sy;brevZ~FQ3bKE(Na0_pu0zrT#9`rAX@pD(wjmr>s_Jq_JxwCBLPWzB` zU0L6X`p$e|JWnG^zwbi015J+QwY}@Iub8#po5$dBY49@`f3Q{ra)&d%X?dW}@S3S7 zhT0NRb3{H1%Ur-W_$Bxpdvvsld&Fx@`?ccrD1O5gHW~CwbO)2H*Oo8@Mp|My_&*7?I&QFD7B04oo68UgsuPP}O; zUHI9;Q&t((gX1R4zQxzV^7$J0gE(cM>UWkT@JH%DNpW1d?&Ix8wKb7LT-PzEI5#3% zh~Rs-z()*hKU2+5=Fpq;tM>;OS6_u#6c~cYee*sNd8+%^-54Ev}SuE^?#TRJ_%_WP| zzLdJK?xje8j;D>u2Syh|qQ^B&7Si!B%XX(!<(r>a*#N`%Pewfmc-`_iU9A$l0MTHv zc>E|`*VvKRfC%`=@bDL_%vb_L_Xv{oC0N_wD3DcuT8 zKpGi6U}OLNfA7f-cCsD3_w(HMeO;f6U^V#nEl;@aOZV7272PNQY3R^?u8(S3_sQq7 zWYPZu%-E9Noi-dDm>^a8z_Gr`dwPR+C-%b-@qG z5cdc8aV{|~(L$##1k`s30mE4NdH*6I70A%K&fMMIHB(2(-4)WpoE+rjC`qX^>Kvd?$ry2Sp_qIQ<{h)@7uU)88h zRBn=ZRXs?&>h}j`%(rBR)lpy5tMp}~$3TzWe(|Isg_LH5Tvd)fT>nE>wq0`xAu*&& zh7&dNo+$Ky{&`!n0d$AKVQFVxHa?7kfxP&zx*^Ve?a83!Ic?*n)N5y|oicIU$>{QO z!7oB0O}PKnfIjenICPr@w3Yl{OPvMxH^Q0);Lv#vywowzepTH*R;d359u&$ks0c2AWHWdoagYZ{e7cJeX8RZd~=rcpb0xv`hj>r0sYp@dT| z5pQ)S86-MoThZpW{r97UlWbeVhW{Eu*>8J1l{71&n`pv8lT;HRJWCOE=Y+PG^u%tk zFlx|7K$C~CI+?g~kYN7j$!58yrn`{b$Bq2=vyRN9rO@2}_5S{ClJA@4G|^)up-U_B zDp;o7JHy-*SA=*WBA%M7;~9oq-#54InoLRd55v~kdoPrM&q?cFpzh!=xbq9`shx$_ zz_MiS_~BsU;TM)q9af>J%+4QFs{B)yp?_0(sUFqehDIjq2&>C_d=CR`?vW~#$;e1W z47_>n5o2}4q%WVFGIWB=*je=>fN-C)?Q2+TzLNplsxZS=GMulx3bKt*RX4BS{iyA4 zX#$JZ(V}^x5#)xwy51l~y&$#926_bX2H)B9u~HiraH-*TkcNm(6W!e*D{65yEFsmL%dB;}^>3GQK6ODZ)mz}0ExN2+7`S+yue6I`fNF=Jt zcfDCEQyX|wF5i3E8pe%9sS?_>W5Pc!4#1spUs(?Ns8QAs=LT>%dE7``$?*dy_UyXD z=i=;FV|ix|C7eI}pnuF76&h#yH9c8`YNgT zKWAr9X(^*eu*_I8x@sk8;MVVuYrE5ySu#{R3$oO5_JLu4#PIWcR?9W9hiy#TqY4nu z&O*+n_nO8-utbn*{4k32(tE5l%JasxxmLB7%nEc;z(G)+Y6GJkn( znCkFI*F#E7La?tV!+T=k%k({*!0p_p$i;+d=Mt8vr5G@W1qUJx&>Ggz66iqbAd3vk zBZ`WzpdyhrEdXK)d2SVMzL-DNW4|jn{uv%#HI*Pq_p@HpS1{f@W*wuy&BZ1tYt*woxYO4`u#mbphJ`>~H0`=ei90)A z>&rCB-4|gq@Qv#)cX{wswAjGlg+cLtBrn4;Be?NJ$l}^Exa(5UgqyLN2Am<9`ESjXeVqLw!bsk@G|;9f7MuHn1e)u-^!>j=B%cI) zmVvP8r$_XO}LsOztITSXI%=~2=uZtH8#-JU&A(LSrDf45ExwE(_1r|hX=ZmV(|Q)uMb5|a{zXD%l8fLI%L&< zT$Qwlf+R|qFXHW65&^IQu*jke!o=gQ_TZbe9D?Y_>3|*nDi1~Z#`xEy1QZiC_&s&Z zdy?v{%i0j#XurutHx3YBU5G{;#ku`39A_7}|JOk1K8>h7&2NDcU72^bovr;Z0*D;~ zePwA*WgR_S#3SbANKZMODa5uIj7?^X`J&qYW+-U5NqYJfUWct6(B3v#20jzsQ(-8? z!JWi!MdjRsVRG=zi(5&h3BDOiy@iQo*qlB$K0tWqd8Ocwt8mb#+%2uMhNQQI_=9hK zvk1y{-&m-C%r>PX*vy<5e}X&g{Tn#rA}x}Z{zO&#BsVwrwAJ&M$r@=;Dg$DCzo5Wk zAULD}{=+B#C+b;=c+go*!xpLK^jxI3f%va3+M;}|{P{)S?97--3s#5#%5(V8fW%QC zRwDEdmd>QgZ&)y>o%>l}pmJ_5`&_es5CPaRRv-MZM4%|5dm?Udg+#^iZqDB=1)1)5 zivvs?AmIjVX`km6Xgh!5;3`+)>{*tJ=l!R@IupJ-q(q0t2q37~JW9X3a*5Gc*qg7| zvhdmets7jiyHUL!TGXlTT1So-Fm1z-e@H31UIq=lhFeh>T5J zkn17;57vxpIa$XL3(nAe8JTm<#vvZBnfnuwwWbN=;T6sMN?}DLJ+(7Gid$ZUThG!C z{Z9EuGC}Yk9T+%(y?qV}4Wl(dD)xzF4ljL}i8;?1hb2mx&L(b{u@`^&RXz^$7WDl_ z*M7VSl^DGf9I^-B`p$m+&zD26u1C-T{^!QVl$)KRo1J|^heO!v z@xb`M~p`GwwfkJl#AWgy=MNOMCCgIEdaDt(bh!Pms|_)!H%p z&d#W<@jzu&$++RgyX>bBKjF)AY{Y%LJn-8mDNo-yx#g-^r(BrU+#Ioc?cZEpU2XG6 z6e`gpt~jFXW#8W%1*xd2sTJPz;xF)1s>HicKd?7s-}yoz5QY5p&-LZMo3+kW+^ZPw zdj2O&#;z|1pXHkB8X!)R8F8Y_w3L;el<05+^-po3EH%d;3aXv65Ab% z#koJuVX$|@#kZ`L_a@93H45hkv${{u1lSQyb!^X66a;<;@g-;p=q!Qm^3iXr4#V`} z)B){DJc(B-e>Vf=o;ZaV-I37Avwn!GlD-4esKKrsZ7so+gBD!x{B&1w1rA-AigGY#{=&-sc*3yH$(MYwjrwjVxPs)r6=@ZP z@A~myFDKPz#D4|1GfeciQwLa|UDOi`nKGGKbMpN}G*Pp=`^DG1sJK5qHS5d!_>!HY zh9fyG$t0&M!;9r>yvBR9qsY^0bw*HfXE6I~eD~<&w%7F^Mj~TW^e7Jt7?n@-Wg?*0 zNBrw(C~xKurt;orIPKC1Qm>AxEhzc+uFaFP=Dm)xD@N9dY~JC9`gB|o8wbY^EN&Ga z_K--uLFa;42LPifbc-u06t9kw;k%?I#vi9o>z$>ZbtXD)je1pnk*OIGps(eZ${_Km zK7T%p?22ivFcdV+QiaeJcN|v>L9gr924brp=Mwv|@56>gU^!V0wBDA|+n#kAUcS^z z?4>q`|CF@-kyz_zS$0kDS0tuNG%V)YKePEBS!0v3!NL4zz3FW;JvIkN>36d2t(d{a zsElSOrB-qWH+S-?poeDR;<#b6-0qvJdLu~GQ-NV4l>97SrcI{6NaR{wT3vk{LO|cs zR%Qqn_d%D#K!jWWF+~#o+zY>|eCrmP{46DEH)8awT0kxL7L3`WNX~LEdX$2|-#8CnrLw2B_ zO@r3;GwsWY-~vsZBwUgyNMdjPFVHhhi@M%FZ8xhXEjkL z{ahntMr#AlWLlqk-wA9l<3;3e41WL%r3j;TlwRsSP$()vVjbmMYRCKI5fF zZj9(bLfo*cNgZwjQBNKhH-IC20ZoYE>U87_T4!H1++;c)?7`*JhQZLgKig)R!F?5i>pz(bTi|HYap)S+&!)RC?-k^g%BrLB4(meTgm zExnMVXR_6JnOt7gUInz+VBs7cpN9H1RV>v|vM~$wr0ThZn#xsewZWj>-R$1+7}Y#$ zAH`n?Fg}G3WHHaB8xKufV$C)z|D@9W$WT2G82YE)3Wu+&SB*a>-`@>+x>VytgP*)7 zn-1J!VbLa12tIYSx+Ln7L0}&U0`>z0ZTxwnSLiy;8C@x@y2_RBQBZ(KDujfzvhi~o zLj5+fu+u(CH>}Q*kw+G}?(0*axSe^63dt9pz(OKy(G{s<*-psHU9b45YRu}`n0I$U zH9}&L3vrQl{P#VHbi*~j`s%wAN{l!IBIrC}1)J+i=WotCV@6!%mlf|av?%z@;RV?7 zA2*K&?fz;%rJr4EfSp^~`ZOihi?G)kgA!D_N(tpT&*dm(w+A4`+&N2~ZAZ}1p8GZ- z*nvw6e!!GaUyN$#9GqXj&XGkBhHg=bY`|)mAUIWsL-eqyYe5Ul#4E%~JdvK7a3_D` zKO4IX9XnG-O>2w1IFyvBS8+(qP1a|(SbYMi=cV(-j+1CvC64%k6m8;^Fl&_v2fq|g zT|{g|+Y{PXSB)>k`oDd#uD+2lQucYtBznZpf5+JWfYu?U;YaO6z)8i-yy4H#SJ&rD zy>So1OdrCS&2hC)@NQ!$K?aA(qKB!@Fmjk%JcNlHweZQwO`EKp0fKs|1@?Xjbn-cV z*{ko|RYOP6?witzt4n>JQkKcM@&8LoHcfj^wgu7r{@-lk*+Tsfkhp<5()z5Itd23YZP~zSwX_)o(yG*xteZMFj5<&^gzjE^0uvFtknuG{pMcfh# zz@_-Ck})Wf%CC?TeJPzwM+kA-7#s}#S9!g|HH$M_{Mb>HT(#d;A`raTk1!H3m2%oY zbUB~BG%sUt1NHdSO)T4<;n~{N|)cC4!BPPa_bZaxTw=A5_ULUWnr+0 z?OWiOJrT%16F|)O-kcfC+rz{19E~i_urp?u=W$xwmD@3Kd)Y5O&9=5VjC#sXuYGVN z$@YB3tJeVUU(J_6W#3v zS#6(alY4!NnI;t4+-^zs^P&!RAXp-e`k`s`1tOGzdeGfFKV&EI24gK~n(6ik{wn$qz=G)0|Ju zXEy!7m(YfNv`~Fo#D|D!8b^op&B_m- z-fS!D;>SEK&bq#hy$=rKq$Zd#%GdI?x^E#?aw?5zS%=lcE>oXA% zCK{Q%MxPv`9cAyyw8;LF>scNlS~l4kRVF@Dq&(fcP)-t6ykekkEbIEw5{Jri>Lm_c z52yC)c$o*9O{F`3>8Wii_ThwtLxMOUf6MY}T#OD~wFfSOEMm_;+x^z$r8+;=>M{6F z6}r;^GtzQU!hi0-2-YZ7;W`3V#LD;1)ywWUj;KPKuAql{!B(mkjIr&jzcCZH-w+jL z2 z*6s@1LNXpt73~62@qkG@VQG}jn35#`3e==S20skD&VL0V37LVGJe@HYF$3|Ks&jO# z#Rp;ogqS+Hxaz7ufojQbBTiWV^!iDRUsFAs{m6=t22~CUj5Od6RkHH&JW9bzs>pxu z<%wSm+x74a1yJOESe+8t=xVP{Ch2@e6bum3lRo{$wJsZ7Jk^5LupIoVe$+BXf@!|G ztE{3-lWxYBsiEn~Jl9m^z+qU(o%&Gi^X0le`7@Kb2V5pBC`13inBH{--A%JQXb)!f zny+vz2*Y*d`iBD}+Ze#|O7vR2yxC$7YzE=c-tFak^V>=WLBJ;M(S|$Ki)?^*Ba}8T zy}yWj4m`BRhbVA#l5mNeSeJ%^<#}$`w4Q~zrJs=3ZfCw_I8u1OxhZN#?JZtcUq%0E z>2_D%!Hg(7NXUZj8KR4S`{xM*1rq~W)A1ZczO$$XzDLQw@1hsNi`cw!x9MynZTS>C z1M!|e(UOXiW~mHHPd?}tf2jZQYjCrcRALj6lP)7h@7QH{(}*v9ESo)O#3lk>(IkBu zHt53$38xMc03Do;vQ@+9%<%p@P&dHO<`KYmrfxtFXamF3l}0WjAb2S~!mE4srp4|; zR27emXv4enn}noSZcx9O2%Pqhm6LzHb*@AM4Y=2q|7~qw#pLK)hr0ICcw={EdI9|3 zk{_IMNGjtaqomOnx&MOFBg$DZFb4MTR^bnZ>ob|2EjwW-u)%)(Ik5}`&odm~8lZG* zB2QV_XiRKvC*^kB(DEmh#-OytBK^U^&g%|Bj z$_%05&3|%9j%8I9oX8u4QHOF(SKs5^rh@Sifz@*WH!Jek7x2H3M-3nh@eX?ifXzx^ zVxokcAB6nUsjg=9(wc=u}abb{LWA4@B%LbJ#-{>2k^bjyh&rz=VtiJXkdsu5c^w! zFp1%3e=Ns3nbf~BCeSQ5H{l{;av3w|!VIb%5h%%6$$M(Zll2^#hq5T>Q=go>6-wqk ztsMsq=$)h<9!Iv7sdF}66m34B1{QH{W!jinm(EFH7QmAeOGoN4UN%f?Av)JrT z?ys0FN}6gW`cuD|(od}vv%zq+&c}xM4`2+opF$B!b_%|E#P{_K3l91xCFq6s_H8;8 zim=wZ`7o39II1w0j=<}7Wk4C4*h4`N3i{zWJ3DS7c!Me#2c<0dA4X)dF8e~uo@|4g z>%ys8&?u#hUrXuMlBlA`L}eTzVBLT}?Wv^=am(Kz3dW9`YZETn*Wd4$n>Vlfyl%`I zCu-#nAx5;#-x4bUVK}l&RD2m}eOaMjTem!DRWEWhLX% zag2T|3z3iHcC=Q}#G7CcwNFLaSxGv3erHRt5Swo_ZP>>HZ+~Vv+d$?IXDy*gWQei{BD`ru<=YZoq((m%8E^T$m?eNd#JFy}W;3(v2! zAswh`>v&4cYslt%#-Q!taIt#+?Q50UaIORh0K8p=EmzJFT5dZ9<)a`=tUmDU)|J9X52gj zKhwokvH{EkBkw<7%OhwV!V(aL4ded)oJ)(#VYmiN=#5o6P+i@xLc&bnESg?i(vbDv zJta9(zOc^8o>$Gk#NJSjwm2v}s_H4{?j>H4ohNRvyrM+udyuZ&09R!+ME$iuGk*_V zWHgBOV7uS;;qLsCn1N2O_W;XBU~7g9`h6HC*XQ*@Vt3ss_2$Y>r~i$g1P8_O>K|mS zm$zGS>Ymc8DlwB9HzEvGcn%|qoh>w3r*ur=r!1QMRJ%B@R5#oc>{`h!pPO)EBnUhw zM3%$TZZ?RFkw*4iL8)WE{`9guxQ&(qI@ex1t-ntaxBiD6aFCo5)w};m=%78UCH!QA{~R}QSs=Bo4V0sfdCn7_jzWT?s} zj?9ZwQ$6qx!g*ddw%1Jl{>n|G@5L68^G2b^7bD}$V)4f89cMI^l%yFUI}N0!7i#w@ zYmD{7s*FdF@Zlqss7E4qoDq?2@BGGmD2_Umwmz=yX;WhPaGqeEi9D2iHK4akiAkvH z4JPK{0|@=0391Z~LEI&~Dg;4{5@ANR`W(#t4p&0dZzswv8}lPvWyGLtmq35>fo}-y z6uQLhjyR%~pbZr`VAk0F>*eOPb|^Y8l7$tR(2wfQSL8t)@e{Gu{Z#pOo|W3V9KKu^ zl3Ci2HU25=WzYqA^JV1d?oH`9)e^y|JGb_Mm~2~|!5dlrb<#VfnW!{!`i+w+3v+>v zyQ22g@+na)p9AGyqW za;|8K7Slu9Up+YHKsoutpg?{G8X1{yC|CCR$pFEKkneFHT5AgI zdeV}inPdMsr#^fqou+SWZDCMzU4Jy0hY({Wsr`N420%@EK6@9p4tg4?&mXSeVx9~6 zdT_11JkJ#l{%A)w6EitXjNPJ5atimEg>q`@t(D(4Mk zo`VciaF%f;H}t>5C^Lr06Cd#mvvV}EY#VfK8}szQMX-pZMh_fkc55{p!<1Bbd#WP( zeBYW-6VTdAqxoIeOy{MNZrQjDO1YQh>OLRbzF{Ew+-iOS2^+ z3m<4+O{?e+YM-roliljZDH35n+=zBbv(jM*`-H6xZ0SNT1^)TaNyFwMg(`ui@`UEy zDDCOUp((M}r_RHhVcNNwW}C;5a333614ma^ikS}wo*(}mM5v>nNBo=wBVtMnEg>*3 z{4zrS2sA>SsSAf9EuPT?>5$u`{;=5J|E6sBnoRlHYM>MPtt!IkphWQMD3R2NC-WyC z?Bdch&Wj-;1@J%keJ9QcDaf6eDMB@0yM>CZJ9W?f8q%6mR^CH1aI0c>_qUT;QJ&cr zr8b%o`O%GUC`Zcy1MssThrk={5`akc&EkixA~rp^32ykjC_n#^@J>-3fRS2_g*XuX z`wVnXdAKK^@8)@hf8|uOKdCX#;4ztjxT?Uz-^C%=^y+mF1->oi{t3z^K8>88pB?fM z$a3L(YVgd{<-fm=9EcW3g8!cP-aQ+&Af2L6Jq-IP`^Ay8{9cIiHb<~?)HG&Io_p}y zj}M<}zwG>B8SkME?MUvr;y)73GaPWIlaI25NJf+u*uQBqOu zQ%;J3zdYMyZGC^r9%zwVhW;E2?fFMDc%-Apc$+oH8ID-K|H*yk#epU)F`6Zq+{2N+ zTDQNcMN3KlF_DB%%a?V__Joc5&>q=kyNL%&Qq#wXrPB_;pgv zO>^#+cE2=Qe3-kDU#*V8hS?ms_2^MH8!^Y7{mKWtK$fB{W50luO z>S`>2M6jSeP}4($yG;V*k{D3zZbP#6)wF8{kg#hMRdq!O8QX0jL#W`jXwuHDt4;~z zX=5wOFJee2Jn23A^<@$MvOrc=_CLhK6(aK;zh&w-_n*-csBUUJl*votR_vNT-`mZ! z$V&5!sJ{xQ6m~}z==O>Cau{n5!~XeFkus1qEYBx|WLn^Xs;i{(O=WN>O=;NHkQ-LL z{NK3vKp*OTtp)+7c&AE-*p4nSTG2G6Vee2r;*cnahRwgvSR!5fEk|fhm!$?-!!4?g z^x4@(C--;k49&$6UJv0UDnDDAS@?)HjY(w5hQ7{o&Q73(3+q zGud?jw)J#%LYc^%YGo7%zgBSr{R~_UyAU}0Fm(wew%ZV{2UQ(v=|1cPg)vheBtSZ@ zSc4uUuKAXtAAi{;5M}oEe*79#D2qa@>DU44flr5oFZ%yFQp)@`z5L{mICK&`q;k3P zXBt!4pca3$sNE#hKh1b}~RSV(Zd zjpcv6_uptW9?6q-+aT*v`CBc?;isj_a-K^wx?6ETXtiPzH09x&HgR#qm?REG&byTk zRkz+nPc&9h`Y#if|3n|s>SYq7tSLi8Ho!o6^NE(Cky{gxXm^@gfB=L*zO< zZYyhYS+yarkE)#DF2`2as`C-^7E#0jP$saq2x-9I;`-CBFr&c0K(hnQAZ-N>8pWiq z{KXHr{pZiLw>Ez-t7H0DwMpU}EL#S-VV*9cyCF+-?*`@|aMr}IOFdED=U+6toi}Ff zd=(~0RAi6q?K=b0(=f_rC5E)hsC))w$w^uMoL~TQXB$qkEpJmiGv{x=oe;P$R=ds zcN>O_x3RuwL$*Y#RK%OEG8~9UBqZBzg(a`)u^s4`KKtft^8!L7+|C))3i ze4`&nmL?c48&$izv&QA$t`aBHP!Pdy5OA>>@H_mc-0$GOax-Rcv=25jYztz%lrXWb zT%l_!YWNl%pK7?xls8ehlVFhp%wPRm(nW%mSJ1V2jJ~h<5%sLS7k8oUUt=ysj2{<~t_zpm3jx`{OL zDCiO{xX%a-pKHAqB?#$*O6UQJQZxAm^WUrZn~RI8gP{Nh3@BR*d55z|HRRr9W&_8b z@A85{2Vo{F|DmFVeD)`Gvx|lvDy#KcJlk~EI!QSlboQPwhk!dYJ)B>TKi{;9 zRW*W1^#@hNsOOBJI!D?s&PbD-f?pDccQ;mAdXdiG_OI{dzqrls$Z{47?ekAv$AnoW zx*Z%;zI1+;Ta^Bf1e}xb#~b^UC&m78v<;+34*-XoUj1@N#}Ha6?>l^Ss2TxZPHG21 zhsjeY=?w!)B9I1Z21Wp<$3)#9uHidb;ckOJsY5PJ1?<%27Nqc5WX1fq!kF(#}%X_JmB4d^{^VwFe-oNqlr zb7r=N;GJCjvOhlxZp6qbYzVBtx#NrHE238gX=U?tn*@vYL)=5vqYBkLB3CkQ9;qny zs)tE1#TQYL5MyTk`$1{P&x2|!?+2)x6I{tU4JX%{ z)UfBB5N-GB$FpY1u1@0p?le05FNc>S^7fn-da8((^fc&)QHjC0z{uL=gF}%%Pf?HMwfe= z+5eKTn(n#q48H!g8s0L(OtwQ;;zogS6)P;&EWZ&~z1q$}?MzGOWzxEeqOOepD^;bw zWA^x|1(A7uF&XbjPgJ!x8K=1awx<`eV(h_-fA05f4Q9Y?0N%13KlKnCQ`CbXfr{PvwMEhF9n=Th6mwjUxTB6n!aF31uBwV=xmqMR8RS0^=D=!i0)ehV}df z6(w_ofh>qtCQEDXswXdz@;D>742Ge(h$J#5PdKQ@kW!%Rr`FIN|Q%hFTsHiPX0 zk9xdBZr)T+BSme#-*P)+I6NR{%mbwyA8I+m65%xlLOh@)p_Rv2#SI;~-s;oQCp4k#Sf~V8$djg)TTvk^sG&5FpF)UXq4H6r zW*IfXTlk2t@j=IsYL;fpuK%K#`>YSL*;o8*p6h4G{$u1_t{|=fx3|(bMvBud^ukfN zm5;?aVe-pj9&9BP#SweSkxU#fY3vvA{l)vc!4h@B59Iz6e?E1)m}SR{fry*vjaErx z@OS^M@r@J8QW3o&^I{g%^hc#CE16pdVO$2(NI;DdovC)TvdP!9FC3zx>9!wLGp&;%u@`N7n z7B-la=RJn)REy-pae8;d+BMT*b=*<-Ikzou?KE0dFyjHoPBN*2rr#-C2JEPTWG zxRt$Sp@>WM3qJQ#QL96!-!p_gDUKmgGh1VB{zh7LoeO~w4vZvqQzDd;>2BT$-$MMb z2IN%~BuJ(S-z=R1jQesPj(b!Y(e&nn)fcasHb+@m!!rZjp!A5v{88X0zPv}VZM-UL z0dJ~)W}4^Ki#so<_B6LK*10X$sR#T`vSlf`57IzZcCZ&WNRq=Yi-HOwrz>O3xz}F( z9T+gjev$dBWmGmT8Ws2LH|qF!0lB~b=FffJg1ad-|LC@7g^*j1xjn!sk85s{1S0s* zcTZiC1TZZdTkYgcRIqBF{XKM% zXXtE=-E75Bame-g0jE35`qV^1jE=<=r&*^z^12N|x`( zTjR+jhDBU;x%oDpuksz%lfNG3jFz)BEq>Ed>Q*V#6?+GTCMQG1s2+_Zyz~dlMid15 z*zqGiY!HN!T76RmNe07S3Z%A-m{#)QaQ>lno^dQN z{egZsUC`)zW{e`VH*faqpIlu70~ZgOr3I$MIXRLjbAxJm5^yR>5!BeAfFtos^aCDV zxB6g}A|t4?RNN)i#oxteCa-Tj5pYf$E-wRl4|Sa?$}_|<=^^&v+lv0%h7K${4atOe zE92%q@IV6az~(~A*dfLfQ$4z1{xdyH`jtHoig9B2e1124g(Unh#Mmt)j!+2q4ktQ8 zk!@S&?G#E6Ilek+@fdT~S64UC#5m1{!+Ew(-h4%!lr*k;s}bpvO>w=Kv2YS7s;q4* z4^lEvE&Qxl^38Spz^PN(n|whF&?0jmGbtL&nQ#6kuLnT<*mhKaRIFcZE-THCX;*RJ zet{MaKVHvT_nqb|j0ulF6!4lKnH5$hBvVAudjg=usiU&@T6|sle^~E z3JtiRsZ0=9l9hWI_EjE&=sk45o=4#|fCV%=<(IREJPVflmXX{IUU(~dIOzg81Ptsp zt!=$7>??-4h~q_mPH*l-$X;GxLtZ)ZX@T;Jf(LnIAkkl^Giwz4 zL|_Xp9KiS@sy~sezQF%wwQfnV8pRwd|t2k2g73^ zErC`Rup|N-z;X`giFr=g&?kfsJ_?u?6Czdx$hhvm?|I($A3%8Yv<j{PALg~m AWB>pF diff --git a/launcher/icons/room-private.png b/launcher/icons/room-private.png deleted file mode 100644 index 33896d9b9efd111c7e7459c330d8fa09b32d2397..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 683 zcmV;c0#yBpP)D%s=-MD!V4@DWrHK@h=1AVehM#gl|!z^mxd zkE4eqN7>{7Hkq0AV9=}zW~MxJ&CbkjKHL-xJzdlM=HLJ7F2Q4AalQ%M0a~0aR#o-o zgZmG^P=e09d;iv=Xva<(QM6;rE3fR?($i}Ir<{FvwmHZCeoCj)K@dd1L=ZrLkT47g z+3;>=;IkXoZ?f`w`8M!T9o(wd8vuw3h#;b<2%=KtR8Un!Kt#f$J|Z}yRaOiTKnQ3U zlWz&#Zo=;F_llVl6K9#8K2Ndc`&9s~vG_4e4_?(~(bt1m}YF}}c+ z=g^B6F6G-Kjw3d=+VpyRAc|n27Vh;Siq-L2QP5~i^_4ZIrYhE{hFa)RR*2+o2uTu` z^0_^VQUH=}(k~)MSrJq!2yAY(`}t3wKOA@?+U>72+@is#g8lr8%70(CAXK1g@qovE z8H7>5VTS`w40x;>DlMac1wn_%Rj?T&FpC`?r`SfN7M006${~tN+nb!M9gD2h>Y;9O z%&D*(ZgA!D)zWp<&}>RwEz7ddCoE&=NR$+>#&HCt7Fe{yVxF0id8;h)VUhWD2Ug39 zpRaGMwPu@h{hlyt#Bs#>#u{mwz9`$u!s7glncWkSTPFdhY5FEj)1}9co~$!e$`E6| zlheQ%;2bdF^Kl>p{s2FLAHXl*H?Z&DI1HS7FaDEp|IwJ|3`iaH*WZUK_z(Sw{h)Tz R5c2>4002ovPDHLkV1lr)JtY7D diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 0263fef96..5eb40861f 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -16,6 +16,7 @@ set(server_SRCS processors/TurnOrderProcessor.cpp CGameHandler.cpp + GlobalLobbyProcessor.cpp ServerSpellCastEnvironment.cpp CVCMIServer.cpp NetPacksServer.cpp @@ -41,6 +42,7 @@ set(server_HEADERS processors/TurnOrderProcessor.h CGameHandler.h + GlobalLobbyProcessor.h ServerSpellCastEnvironment.h CVCMIServer.h LobbyNetPackVisitors.h diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index b9e3ee66d..f6643a803 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -8,7 +8,7 @@ * */ #include "StdInc.h" -#include +#include "CVCMIServer.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/campaign/CampaignState.h" @@ -21,7 +21,6 @@ #include "../lib/spells/CSpellHandler.h" #include "../lib/CCreatureHandler.h" #include "zlib.h" -#include "CVCMIServer.h" #include "../lib/StartInfo.h" #include "../lib/mapping/CMapHeader.h" #include "../lib/rmg/CMapGenOptions.h" @@ -34,6 +33,7 @@ #include "../lib/VCMI_Lib.h" #include "../lib/VCMIDirs.h" #include "CGameHandler.h" +#include "GlobalLobbyProcessor.h" #include "processors/PlayerMessageProcessor.h" #include "../lib/mapping/CMapInfo.h" #include "../lib/GameConstants.h" @@ -53,6 +53,7 @@ #include #include #include +#include #include "../lib/gameState/CGameState.h" @@ -165,6 +166,7 @@ CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts) networkHandler = INetworkHandler::createHandler(); networkServer = networkHandler->createServerTCP(*this); networkServer->start(port); + establishOutgoingConnection(); logNetwork->info("Listening for connections at port %d", port); } @@ -172,9 +174,6 @@ CVCMIServer::~CVCMIServer() = default; void CVCMIServer::onNewConnection(const std::shared_ptr & connection) { - if (activeConnections.empty()) - establishOutgoingConnection(); - if(state == EServerState::LOBBY) { activeConnections.push_back(std::make_shared(connection)); @@ -195,16 +194,6 @@ void CVCMIServer::onPacketReceived(const std::shared_ptr & c pack->visit(visitor); } -void CVCMIServer::onConnectionFailed(const std::string & errorMessage) -{ - //TODO: handle failure to connect to lobby -} - -void CVCMIServer::onConnectionEstablished(const std::shared_ptr &) -{ - //TODO: handle connection to lobby - login? -} - void CVCMIServer::setState(EServerState value) { state = value; @@ -262,14 +251,8 @@ void CVCMIServer::onTimer() void CVCMIServer::establishOutgoingConnection() { - if(!cmdLineOptions.count("lobby")) - return; - - std::string hostname = settings["lobby"]["hostname"].String(); - int16_t port = settings["lobby"]["port"].Integer(); - - outgoingConnection = networkHandler->createClientTCP(*this); - outgoingConnection->start(hostname, port); + if(cmdLineOptions.count("lobby")) + lobbyProcessor = std::make_unique(*this); } void CVCMIServer::prepareToRestart() diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 346d135d7..91d1c3553 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -37,6 +37,7 @@ VCMI_LIB_NAMESPACE_END class CGameHandler; class CBaseForServerApply; class CBaseForGHApply; +class GlobalLobbyProcessor; enum class EServerState : ui8 { @@ -47,15 +48,11 @@ enum class EServerState : ui8 SHUTDOWN }; -class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INetworkClientListener, public INetworkTimerListener +class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INetworkTimerListener { - std::unique_ptr networkHandler; - /// Network server instance that receives and processes incoming connections on active socket std::unique_ptr networkServer; - - /// Outgoing connection established by this server to game lobby for proxy mode (only in lobby game) - std::unique_ptr outgoingConnection; + std::unique_ptr lobbyProcessor; std::chrono::steady_clock::time_point gameplayStartTime; std::chrono::steady_clock::time_point lastTimerUpdateTime; @@ -64,6 +61,8 @@ public: /// List of all active connections std::vector> activeConnections; + std::unique_ptr networkHandler; + private: bool restartGameplay; // FIXME: this is just a hack @@ -75,8 +74,6 @@ private: void onDisconnected(const std::shared_ptr & connection) override; void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; void onNewConnection(const std::shared_ptr &) override; - void onConnectionFailed(const std::string & errorMessage) override; - void onConnectionEstablished(const std::shared_ptr &) override; void onTimer() override; void establishOutgoingConnection(); diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp new file mode 100644 index 000000000..5138f1b61 --- /dev/null +++ b/server/GlobalLobbyProcessor.cpp @@ -0,0 +1,82 @@ +/* + * GlobalLobbyProcessor.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "GlobalLobbyProcessor.h" + +#include "CVCMIServer.h" +#include "../lib/CConfigHandler.h" + +GlobalLobbyProcessor::GlobalLobbyProcessor(CVCMIServer & owner) + : owner(owner) + , networkClient(owner.networkHandler->createClientTCP(*this)) +{ + std::string hostname = settings["lobby"]["hostname"].String(); + int16_t port = settings["lobby"]["port"].Integer(); + networkClient->start(hostname, port); + logGlobal->info("Connecting to lobby server"); +} + +void GlobalLobbyProcessor::onDisconnected(const std::shared_ptr & connection) +{ + throw std::runtime_error("Lost connection to a lobby server!"); +} + +void GlobalLobbyProcessor::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) +{ + JsonNode json(message.data(), message.size()); + + if(json["type"].String() == "loginFailed") + return receiveLoginFailed(json); + + if(json["type"].String() == "loginSuccess") + return receiveLoginSuccess(json); + + throw std::runtime_error("Received unexpected message from lobby server: " + json["type"].String()); +} + +void GlobalLobbyProcessor::receiveLoginFailed(const JsonNode & json) +{ + throw std::runtime_error("Failed to login into a lobby server!"); +} + +void GlobalLobbyProcessor::receiveLoginSuccess(const JsonNode & json) +{ + // no-op, wait just for any new commands from lobby + logGlobal->info("Succesfully connected to lobby server"); +} + +void GlobalLobbyProcessor::onConnectionFailed(const std::string & errorMessage) +{ + throw std::runtime_error("Failed to connect to a lobby server!"); +} + +void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr &) +{ + logGlobal->info("Connection to lobby server established"); + + JsonNode toSend; + toSend["type"].String() = "serverLogin"; + toSend["accountID"] = settings["lobby"]["accountID"]; + toSend["accountCookie"] = settings["lobby"]["accountCookie"]; + sendMessage(toSend); +} + +void GlobalLobbyProcessor::sendMessage(const JsonNode & data) +{ + std::string payloadString = data.toJson(true); + + // FIXME: find better approach + uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); + uint8_t * payloadEnd = payloadBegin + payloadString.size(); + + std::vector payloadBuffer(payloadBegin, payloadEnd); + + networkClient->sendPacket(payloadBuffer); +} diff --git a/server/GlobalLobbyProcessor.h b/server/GlobalLobbyProcessor.h new file mode 100644 index 000000000..530e59161 --- /dev/null +++ b/server/GlobalLobbyProcessor.h @@ -0,0 +1,39 @@ +/* + * GlobalLobbyProcessor.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../lib/network/NetworkInterface.h" + +VCMI_LIB_NAMESPACE_BEGIN +class JsonNode; +VCMI_LIB_NAMESPACE_END + +class CVCMIServer; + +class GlobalLobbyProcessor : public INetworkClientListener +{ + CVCMIServer & owner; + + std::shared_ptr controlConnection; +// std::set> proxyConnections; + std::unique_ptr networkClient; + + void onDisconnected(const std::shared_ptr & connection) override; + void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; + void onConnectionFailed(const std::string & errorMessage) override; + void onConnectionEstablished(const std::shared_ptr &) override; + + void sendMessage(const JsonNode & data); + + void receiveLoginFailed(const JsonNode & json); + void receiveLoginSuccess(const JsonNode & json); +public: + GlobalLobbyProcessor(CVCMIServer & owner); +}; From 709905b1a0087d8ad48d24b7c2fcd4bc645522a8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 12 Jan 2024 16:55:36 +0200 Subject: [PATCH 34/98] Simplified networking API --- client/CServerHandler.cpp | 14 +++-- client/CServerHandler.h | 5 +- client/globalLobby/GlobalLobbyClient.cpp | 21 ++++--- client/globalLobby/GlobalLobbyClient.h | 4 +- cmake_modules/VCMI_lib.cmake | 2 - lib/network/NetworkClient.cpp | 70 ------------------------ lib/network/NetworkClient.h | 41 -------------- lib/network/NetworkHandler.cpp | 20 ++++++- lib/network/NetworkHandler.h | 2 +- lib/network/NetworkInterface.h | 7 ++- server/GlobalLobbyProcessor.cpp | 16 ++++-- server/GlobalLobbyProcessor.h | 2 +- 12 files changed, 62 insertions(+), 142 deletions(-) delete mode 100644 lib/network/NetworkClient.cpp delete mode 100644 lib/network/NetworkClient.h diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index a592ba61d..f6ce8a5c4 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -138,9 +138,8 @@ CServerHandler::~CServerHandler() CServerHandler::CServerHandler() : state(EClientState::NONE) , networkHandler(INetworkHandler::createHandler()) - , networkClient(networkHandler->createClientTCP(*this)) , applier(std::make_unique>()) - , lobbyClient(std::make_unique(networkHandler)) + , lobbyClient(std::make_unique()) , client(nullptr) , loadMode(0) , campaignStateToSend(nullptr) @@ -268,7 +267,7 @@ void CServerHandler::connectToServer(const std::string & addr, const ui16 port) serverPort->Integer() = port; } - networkClient->start(addr, port); + networkHandler->connectToRemote(*this, addr, port); } void CServerHandler::onConnectionFailed(const std::string & errorMessage) @@ -296,11 +295,13 @@ void CServerHandler::onTimer() } assert(isServerLocal()); - networkClient->start(getLocalHostname(), getLocalPort()); + networkHandler->connectToRemote(*this, getLocalHostname(), getLocalPort()); } void CServerHandler::onConnectionEstablished(const std::shared_ptr & netConnection) { + networkConnection = netConnection; + logNetwork->info("Connection established"); c = std::make_shared(netConnection); c->uuid = uuid; @@ -868,8 +869,11 @@ void CServerHandler::onPacketReceived(const std::shared_ptr } } -void CServerHandler::onDisconnected(const std::shared_ptr &) +void CServerHandler::onDisconnected(const std::shared_ptr & connection) { + assert(networkConnection == connection); + networkConnection.reset(); + if(state == EClientState::DISCONNECTING) { logNetwork->info("Successfully closed connection to server, ending listening thread!"); diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 7b761d2cc..0a5999c0e 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -86,8 +86,7 @@ class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClien { friend class ApplyOnLobbyHandlerNetPackVisitor; - std::unique_ptr networkHandler; - std::unique_ptr networkClient; + std::shared_ptr networkConnection; std::unique_ptr lobbyClient; std::unique_ptr> applier; std::shared_ptr mapToStart; @@ -113,6 +112,8 @@ class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClien bool isServerLocal() const; public: + std::unique_ptr networkHandler; + std::shared_ptr c; std::atomic state; diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index 491d8f4e1..66877ef82 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -17,17 +17,15 @@ #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" #include "../windows/InfoWindows.h" +#include "../CServerHandler.h" #include "../../lib/CConfigHandler.h" #include "../../lib/MetaString.h" #include "../../lib/TextOperations.h" +GlobalLobbyClient::GlobalLobbyClient() = default; GlobalLobbyClient::~GlobalLobbyClient() = default; -GlobalLobbyClient::GlobalLobbyClient(const std::unique_ptr & handler) - : networkClient(handler->createClientTCP(*this)) -{} - static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0) { // FIXME: better/unified way to format date @@ -149,8 +147,10 @@ void GlobalLobbyClient::receiveActiveAccounts(const JsonNode & json) //} } -void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr &) +void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr & connection) { + networkConnection = connection; + JsonNode toSend; std::string accountID = settings["lobby"]["accountID"].String(); @@ -189,8 +189,11 @@ void GlobalLobbyClient::onConnectionFailed(const std::string & errorMessage) loginWindowPtr->onConnectionFailed(errorMessage); } -void GlobalLobbyClient::onDisconnected(const std::shared_ptr &) +void GlobalLobbyClient::onDisconnected(const std::shared_ptr & connection) { + assert(connection == networkConnection); + networkConnection.reset(); + GH.windows().popWindows(1); CInfoWindow::showInfoDialog("Connection to game lobby was lost!", {}); } @@ -205,19 +208,19 @@ void GlobalLobbyClient::sendMessage(const JsonNode & data) std::vector payloadBuffer(payloadBegin, payloadEnd); - networkClient->sendPacket(payloadBuffer); + networkConnection->sendPacket(payloadBuffer); } void GlobalLobbyClient::connect() { std::string hostname = settings["lobby"]["hostname"].String(); int16_t port = settings["lobby"]["port"].Integer(); - networkClient->start(hostname, port); + CSH->networkHandler->connectToRemote(*this, hostname, port); } bool GlobalLobbyClient::isConnected() { - return networkClient->isConnected(); + return networkConnection != nullptr; } std::shared_ptr GlobalLobbyClient::createLoginWindow() diff --git a/client/globalLobby/GlobalLobbyClient.h b/client/globalLobby/GlobalLobbyClient.h index 92569c3dd..ca54c0535 100644 --- a/client/globalLobby/GlobalLobbyClient.h +++ b/client/globalLobby/GlobalLobbyClient.h @@ -20,7 +20,7 @@ class GlobalLobbyWindow; class GlobalLobbyClient : public INetworkClientListener, boost::noncopyable { - std::unique_ptr networkClient; + std::shared_ptr networkConnection; std::weak_ptr loginWindow; std::weak_ptr lobbyWindow; @@ -42,7 +42,7 @@ class GlobalLobbyClient : public INetworkClientListener, boost::noncopyable void receiveActiveAccounts(const JsonNode & json); public: - explicit GlobalLobbyClient(const std::unique_ptr & handler); + explicit GlobalLobbyClient(); ~GlobalLobbyClient(); void sendMessage(const JsonNode & data); diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 65549110c..e76542a87 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -124,7 +124,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/modding/IdentifierStorage.cpp ${MAIN_LIB_DIR}/modding/ModUtility.cpp - ${MAIN_LIB_DIR}/network/NetworkClient.cpp ${MAIN_LIB_DIR}/network/NetworkConnection.cpp ${MAIN_LIB_DIR}/network/NetworkHandler.cpp ${MAIN_LIB_DIR}/network/NetworkServer.cpp @@ -476,7 +475,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/modding/ModUtility.h ${MAIN_LIB_DIR}/modding/ModVerificationInfo.h - ${MAIN_LIB_DIR}/network/NetworkClient.h ${MAIN_LIB_DIR}/network/NetworkConnection.h ${MAIN_LIB_DIR}/network/NetworkDefines.h ${MAIN_LIB_DIR}/network/NetworkHandler.h diff --git a/lib/network/NetworkClient.cpp b/lib/network/NetworkClient.cpp deleted file mode 100644 index 888660b3d..000000000 --- a/lib/network/NetworkClient.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * NetworkClient.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "NetworkClient.h" -#include "NetworkConnection.h" - -VCMI_LIB_NAMESPACE_BEGIN - -NetworkClient::NetworkClient(INetworkClientListener & listener, const std::shared_ptr & context) - : io(context) - , socket(std::make_shared(*context)) - , listener(listener) -{ -} - -void NetworkClient::start(const std::string & host, uint16_t port) -{ - if (isConnected()) - throw std::runtime_error("Attempting to connect while already connected!"); - - boost::asio::ip::tcp::resolver resolver(*io); - auto endpoints = resolver.resolve(host, std::to_string(port)); - - boost::asio::async_connect(*socket, endpoints, std::bind(&NetworkClient::onConnected, this, _1)); -} - -void NetworkClient::onConnected(const boost::system::error_code & ec) -{ - if (ec) - { - listener.onConnectionFailed(ec.message()); - return; - } - - connection = std::make_shared(*this, socket); - connection->start(); - - listener.onConnectionEstablished(connection); -} - -bool NetworkClient::isConnected() const -{ - return connection != nullptr; -} - -void NetworkClient::sendPacket(const std::vector & message) -{ - connection->sendPacket(message); -} - -void NetworkClient::onDisconnected(const std::shared_ptr & connection) -{ - this->connection.reset(); - listener.onDisconnected(connection); -} - -void NetworkClient::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) -{ - listener.onPacketReceived(connection, message); -} - - -VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkClient.h b/lib/network/NetworkClient.h deleted file mode 100644 index 301d3d00d..000000000 --- a/lib/network/NetworkClient.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * NetworkClient.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "NetworkDefines.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class NetworkConnection; - -class NetworkClient : public INetworkConnectionListener, public INetworkClient -{ - std::shared_ptr io; - std::shared_ptr socket; - std::shared_ptr connection; - - INetworkClientListener & listener; - - void onConnected(const boost::system::error_code & ec); - - void onDisconnected(const std::shared_ptr & connection) override; - void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; - -public: - NetworkClient(INetworkClientListener & listener, const std::shared_ptr & context); - - bool isConnected() const override; - - void sendPacket(const std::vector & message) override; - - void start(const std::string & host, uint16_t port) override; -}; - -VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkHandler.cpp b/lib/network/NetworkHandler.cpp index ed5503f73..ddb15e091 100644 --- a/lib/network/NetworkHandler.cpp +++ b/lib/network/NetworkHandler.cpp @@ -10,8 +10,8 @@ #include "StdInc.h" #include "NetworkHandler.h" -#include "NetworkClient.h" #include "NetworkServer.h" +#include "NetworkConnection.h" VCMI_LIB_NAMESPACE_BEGIN @@ -29,9 +29,23 @@ std::unique_ptr NetworkHandler::createServerTCP(INetworkServerLi return std::make_unique(listener, io); } -std::unique_ptr NetworkHandler::createClientTCP(INetworkClientListener & listener) +void NetworkHandler::connectToRemote(INetworkClientListener & listener, const std::string & host, uint16_t port) { - return std::make_unique(listener, io); + auto socket = std::make_shared(*io); + boost::asio::ip::tcp::resolver resolver(*io); + auto endpoints = resolver.resolve(host, std::to_string(port)); + boost::asio::async_connect(*socket, endpoints, [socket, &listener](const boost::system::error_code& error, const boost::asio::ip::tcp::endpoint& endpoint) + { + if (error) + { + listener.onConnectionFailed(error.message()); + return; + } + auto connection = std::make_shared(listener, socket); + connection->start(); + + listener.onConnectionEstablished(connection); + }); } void NetworkHandler::run() diff --git a/lib/network/NetworkHandler.h b/lib/network/NetworkHandler.h index d41933869..c5b326c67 100644 --- a/lib/network/NetworkHandler.h +++ b/lib/network/NetworkHandler.h @@ -21,7 +21,7 @@ public: NetworkHandler(); std::unique_ptr createServerTCP(INetworkServerListener & listener) override; - std::unique_ptr createClientTCP(INetworkClientListener & listener) override; + void connectToRemote(INetworkClientListener & listener, const std::string & host, uint16_t port) override; void createTimer(INetworkTimerListener & listener, std::chrono::milliseconds duration) override; void run() override; diff --git a/lib/network/NetworkInterface.h b/lib/network/NetworkInterface.h index 4d3072992..3fb98006f 100644 --- a/lib/network/NetworkInterface.h +++ b/lib/network/NetworkInterface.h @@ -30,7 +30,6 @@ public: virtual bool isConnected() const = 0; virtual void sendPacket(const std::vector & message) = 0; - virtual void start(const std::string & host, uint16_t port) = 0; }; /// Base class for incoming connections support @@ -91,9 +90,13 @@ public: virtual std::unique_ptr createServerTCP(INetworkServerListener & listener) = 0; /// Creates an instance of TCP client that allows to establish single outgoing connection to a remote port - virtual std::unique_ptr createClientTCP(INetworkClientListener & listener) = 0; + /// On success: INetworkTimerListener::onConnectionEstablished() will be called, established connection provided as parameter + /// On failure: INetworkTimerListener::onConnectionFailed will be called with human-readable error message + virtual void connectToRemote(INetworkClientListener & listener, const std::string & host, uint16_t port) = 0; /// Creates a timer that will be called once, after specified interval has passed + /// On success: INetworkTimerListener::onTimer() will be called + /// On failure: no-op virtual void createTimer(INetworkTimerListener & listener, std::chrono::milliseconds duration) = 0; /// Starts network processing on this thread. Does not returns until networking processing has been terminated diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index 5138f1b61..33c7a959d 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -15,11 +15,10 @@ GlobalLobbyProcessor::GlobalLobbyProcessor(CVCMIServer & owner) : owner(owner) - , networkClient(owner.networkHandler->createClientTCP(*this)) { std::string hostname = settings["lobby"]["hostname"].String(); int16_t port = settings["lobby"]["port"].Integer(); - networkClient->start(hostname, port); + owner.networkHandler->connectToRemote(*this, hostname, port); logGlobal->info("Connecting to lobby server"); } @@ -38,6 +37,9 @@ void GlobalLobbyProcessor::onPacketReceived(const std::shared_ptrinfo("Succesfully connected to lobby server"); } +void GlobalLobbyProcessor::receiveAccountJoinsRoom(const JsonNode & json) +{ + // TODO: establish new connection to lobby, login, and transfer connection to our owner +} + void GlobalLobbyProcessor::onConnectionFailed(const std::string & errorMessage) { throw std::runtime_error("Failed to connect to a lobby server!"); } -void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr &) +void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr & connection) { + controlConnection = connection; logGlobal->info("Connection to lobby server established"); JsonNode toSend; @@ -78,5 +86,5 @@ void GlobalLobbyProcessor::sendMessage(const JsonNode & data) std::vector payloadBuffer(payloadBegin, payloadEnd); - networkClient->sendPacket(payloadBuffer); + controlConnection->sendPacket(payloadBuffer); } diff --git a/server/GlobalLobbyProcessor.h b/server/GlobalLobbyProcessor.h index 530e59161..b6e24fb36 100644 --- a/server/GlobalLobbyProcessor.h +++ b/server/GlobalLobbyProcessor.h @@ -23,7 +23,6 @@ class GlobalLobbyProcessor : public INetworkClientListener std::shared_ptr controlConnection; // std::set> proxyConnections; - std::unique_ptr networkClient; void onDisconnected(const std::shared_ptr & connection) override; void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; @@ -34,6 +33,7 @@ class GlobalLobbyProcessor : public INetworkClientListener void receiveLoginFailed(const JsonNode & json); void receiveLoginSuccess(const JsonNode & json); + void receiveAccountJoinsRoom(const JsonNode & json); public: GlobalLobbyProcessor(CVCMIServer & owner); }; From 033b2889c478b03d062c0ddc0fe695fb852e4cbf Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 12 Jan 2024 17:21:59 +0200 Subject: [PATCH 35/98] Add proxy connection mode for lobby rooms --- CI/linux/before_install.sh | 2 +- CMakeLists.txt | 7 ++++- server/CVCMIServer.h | 13 +++++---- server/GlobalLobbyProcessor.cpp | 49 +++++++++++++++++++++++++++------ server/GlobalLobbyProcessor.h | 4 ++- 5 files changed, 57 insertions(+), 18 deletions(-) diff --git a/CI/linux/before_install.sh b/CI/linux/before_install.sh index e08075d7d..345634134 100644 --- a/CI/linux/before_install.sh +++ b/CI/linux/before_install.sh @@ -7,4 +7,4 @@ sudo apt-get install libboost-all-dev \ libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \ qtbase5-dev \ ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev \ -libminizip-dev libfuzzylite-dev qttools5-dev # Optional dependencies +libminizip-dev libfuzzylite-dev qttools5-dev libsqlite3-dev # Optional dependencies diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cc8db9cf..eb27ea4f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ if(NOT CMAKE_BUILD_TYPE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo) endif() +set(buildLobby OFF) set(singleProcess OFF) set(staticAI OFF) if(ANDROID) @@ -89,8 +90,12 @@ if(NOT APPLE_IOS AND NOT ANDROID) option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF) endif() +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(buildLobby ON) +endif() + if(NOT APPLE_IOS AND NOT ANDROID) - option(ENABLE_LOBBY "Enable compilation of lobby server" ON) + option(ENABLE_LOBBY "Enable compilation of lobby server" ${buildLobby}) endif() option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" OFF) diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 91d1c3553..7b366339b 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -70,12 +70,6 @@ private: std::shared_ptr> applier; EServerState state; - // INetworkListener impl - void onDisconnected(const std::shared_ptr & connection) override; - void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; - void onNewConnection(const std::shared_ptr &) override; - void onTimer() override; - void establishOutgoingConnection(); std::shared_ptr findConnection(const std::shared_ptr &); @@ -84,6 +78,13 @@ private: ui8 currentPlayerId; public: + // INetworkListener impl + void onDisconnected(const std::shared_ptr & connection) override; + void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; + void onNewConnection(const std::shared_ptr &) override; + void onTimer() override; + + std::shared_ptr gh; boost::program_options::variables_map cmdLineOptions; diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index 33c7a959d..2ed1bd2e8 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -15,11 +15,16 @@ GlobalLobbyProcessor::GlobalLobbyProcessor(CVCMIServer & owner) : owner(owner) +{ + logGlobal->info("Connecting to lobby server"); + establishNewConnection(); +} + +void GlobalLobbyProcessor::establishNewConnection() { std::string hostname = settings["lobby"]["hostname"].String(); int16_t port = settings["lobby"]["port"].Integer(); owner.networkHandler->connectToRemote(*this, hostname, port); - logGlobal->info("Connecting to lobby server"); } void GlobalLobbyProcessor::onDisconnected(const std::shared_ptr & connection) @@ -56,7 +61,12 @@ void GlobalLobbyProcessor::receiveLoginSuccess(const JsonNode & json) void GlobalLobbyProcessor::receiveAccountJoinsRoom(const JsonNode & json) { - // TODO: establish new connection to lobby, login, and transfer connection to our owner + std::string accountID = json["accountID"].String(); + + assert(proxyConnections.count(accountID) == 0); + + proxyConnections[accountID] = nullptr; + establishNewConnection(); } void GlobalLobbyProcessor::onConnectionFailed(const std::string & errorMessage) @@ -66,14 +76,35 @@ void GlobalLobbyProcessor::onConnectionFailed(const std::string & errorMessage) void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr & connection) { - controlConnection = connection; - logGlobal->info("Connection to lobby server established"); + if (controlConnection == nullptr) + { + controlConnection = connection; + logGlobal->info("Connection to lobby server established"); - JsonNode toSend; - toSend["type"].String() = "serverLogin"; - toSend["accountID"] = settings["lobby"]["accountID"]; - toSend["accountCookie"] = settings["lobby"]["accountCookie"]; - sendMessage(toSend); + JsonNode toSend; + toSend["type"].String() = "serverLogin"; + toSend["accountID"] = settings["lobby"]["accountID"]; + toSend["accountCookie"] = settings["lobby"]["accountCookie"]; + sendMessage(toSend); + } + else + { + // Proxy connection for a player + std::string accountID; + for (auto const & proxies : proxyConnections) + if (proxies.second == nullptr) + accountID = proxies.first; + + JsonNode toSend; + toSend["type"].String() = "serverProxyLogin"; + toSend["gameRoomID"].String() = ""; + toSend["accountID"].String() = accountID; + toSend["accountCookie"] = settings["lobby"]["accountCookie"]; + sendMessage(toSend); + + proxyConnections[accountID] = connection; + owner.onNewConnection(connection); + } } void GlobalLobbyProcessor::sendMessage(const JsonNode & data) diff --git a/server/GlobalLobbyProcessor.h b/server/GlobalLobbyProcessor.h index b6e24fb36..dd53381dc 100644 --- a/server/GlobalLobbyProcessor.h +++ b/server/GlobalLobbyProcessor.h @@ -22,7 +22,7 @@ class GlobalLobbyProcessor : public INetworkClientListener CVCMIServer & owner; std::shared_ptr controlConnection; -// std::set> proxyConnections; + std::map> proxyConnections; void onDisconnected(const std::shared_ptr & connection) override; void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; @@ -34,6 +34,8 @@ class GlobalLobbyProcessor : public INetworkClientListener void receiveLoginFailed(const JsonNode & json); void receiveLoginSuccess(const JsonNode & json); void receiveAccountJoinsRoom(const JsonNode & json); + + void establishNewConnection(); public: GlobalLobbyProcessor(CVCMIServer & owner); }; From 93d78edfb9ec6503987eee5c794868e10f600f10 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 12 Jan 2024 18:07:58 +0200 Subject: [PATCH 36/98] Fix build --- CI/linux-qt6/before_install.sh | 2 +- launcher/translation/chinese.ts | 1 - lobby/SQLiteConnection.cpp | 392 ++++++++++++++++---------------- lobby/SQLiteConnection.h | 230 +++++++++---------- 4 files changed, 312 insertions(+), 313 deletions(-) diff --git a/CI/linux-qt6/before_install.sh b/CI/linux-qt6/before_install.sh index 689101138..df82f65ac 100644 --- a/CI/linux-qt6/before_install.sh +++ b/CI/linux-qt6/before_install.sh @@ -7,4 +7,4 @@ sudo apt-get install libboost-all-dev \ libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \ qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools \ ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev \ -libminizip-dev libfuzzylite-dev # Optional dependencies +libminizip-dev libfuzzylite-dev libsqlite3-dev # Optional dependencies diff --git a/launcher/translation/chinese.ts b/launcher/translation/chinese.ts index 5589be173..4aa44cf23 100644 --- a/launcher/translation/chinese.ts +++ b/launcher/translation/chinese.ts @@ -1027,7 +1027,6 @@ Heroes® of Might and Magic® III HD is currently not supported! - 模组不匹配 MainWindow diff --git a/lobby/SQLiteConnection.cpp b/lobby/SQLiteConnection.cpp index 3fd70a363..afa8926b9 100644 --- a/lobby/SQLiteConnection.cpp +++ b/lobby/SQLiteConnection.cpp @@ -1,196 +1,196 @@ -/* - * SQLiteConnection.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "SQLiteConnection.h" - -#include - -[[noreturn]] static void handleSQLiteError(sqlite3 * connection) -{ - const char * message = sqlite3_errmsg(connection); - throw std::runtime_error(std::string("SQLite error: ") + message); -} - -static void checkSQLiteError(sqlite3 * connection, int result) -{ - if(result != SQLITE_OK) - handleSQLiteError(connection); -} - -SQLiteStatement::SQLiteStatement(SQLiteInstance & instance, sqlite3_stmt * statement) - : m_instance(instance) - , m_statement(statement) -{ -} - -SQLiteStatement::~SQLiteStatement() -{ - int result = sqlite3_finalize(m_statement); - checkSQLiteError(m_instance.m_connection, result); -} - -bool SQLiteStatement::execute() -{ - int result = sqlite3_step(m_statement); - - switch(result) - { - case SQLITE_DONE: - return false; - case SQLITE_ROW: - return true; - default: - checkSQLiteError(m_instance.m_connection, result); - return false; - } -} - -void SQLiteStatement::reset() -{ - int result = sqlite3_reset(m_statement); - checkSQLiteError(m_instance.m_connection, result); -} - -void SQLiteStatement::clear() -{ - int result = sqlite3_clear_bindings(m_statement); - checkSQLiteError(m_instance.m_connection, result); -} - -void SQLiteStatement::setBindSingle(size_t index, const double & value) -{ - int result = sqlite3_bind_double(m_statement, static_cast(index), value); - checkSQLiteError(m_instance.m_connection, result); -} - -void SQLiteStatement::setBindSingle(size_t index, const bool & value) -{ - int result = sqlite3_bind_int(m_statement, static_cast(value), value); - checkSQLiteError(m_instance.m_connection, result); -} - -void SQLiteStatement::setBindSingle(size_t index, const uint8_t & value) -{ - int result = sqlite3_bind_int(m_statement, static_cast(index), value); - checkSQLiteError(m_instance.m_connection, result); -} - -void SQLiteStatement::setBindSingle(size_t index, const uint16_t & value) -{ - int result = sqlite3_bind_int(m_statement, static_cast(index), value); - checkSQLiteError(m_instance.m_connection, result); -} -void SQLiteStatement::setBindSingle(size_t index, const uint32_t & value) -{ - int result = sqlite3_bind_int(m_statement, static_cast(index), value); - checkSQLiteError(m_instance.m_connection, result); -} - -void SQLiteStatement::setBindSingle(size_t index, const int32_t & value) -{ - int result = sqlite3_bind_int(m_statement, static_cast(index), value); - checkSQLiteError(m_instance.m_connection, result); -} - -void SQLiteStatement::setBindSingle(size_t index, const int64_t & value) -{ - int result = sqlite3_bind_int64(m_statement, static_cast(index), value); - checkSQLiteError(m_instance.m_connection, result); -} - -void SQLiteStatement::setBindSingle(size_t index, const std::string & value) -{ - int result = sqlite3_bind_text(m_statement, static_cast(index), value.data(), static_cast(value.size()), SQLITE_STATIC); - checkSQLiteError(m_instance.m_connection, result); -} - -void SQLiteStatement::setBindSingle(size_t index, const char * value) -{ - int result = sqlite3_bind_text(m_statement, static_cast(index), value, -1, SQLITE_STATIC); - checkSQLiteError(m_instance.m_connection, result); -} - -void SQLiteStatement::getColumnSingle(size_t index, double & value) -{ - value = sqlite3_column_double(m_statement, static_cast(index)); -} - -void SQLiteStatement::getColumnSingle(size_t index, bool & value) -{ - value = sqlite3_column_int(m_statement, static_cast(index)) != 0; -} - -void SQLiteStatement::getColumnSingle(size_t index, uint8_t & value) -{ - value = static_cast(sqlite3_column_int(m_statement, static_cast(index))); -} - -void SQLiteStatement::getColumnSingle(size_t index, uint16_t & value) -{ - value = static_cast(sqlite3_column_int(m_statement, static_cast(index))); -} - -void SQLiteStatement::getColumnSingle(size_t index, int32_t & value) -{ - value = sqlite3_column_int(m_statement, static_cast(index)); -} - -void SQLiteStatement::getColumnSingle(size_t index, uint32_t & value) -{ - value = sqlite3_column_int(m_statement, static_cast(index)); -} - -void SQLiteStatement::getColumnSingle(size_t index, int64_t & value) -{ - value = sqlite3_column_int64(m_statement, static_cast(index)); -} - -void SQLiteStatement::getColumnSingle(size_t index, std::string & value) -{ - const auto * value_raw = sqlite3_column_text(m_statement, static_cast(index)); - value = reinterpret_cast(value_raw); -} - -SQLiteInstancePtr SQLiteInstance::open(const boost::filesystem::path & db_path, bool allow_write) -{ - int flags = allow_write ? (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) : SQLITE_OPEN_READONLY; - - sqlite3 * connection; - int result = sqlite3_open_v2(db_path.c_str(), &connection, flags, nullptr); - - if(result == SQLITE_OK) - return SQLiteInstancePtr(new SQLiteInstance(connection)); - - sqlite3_close(connection); - handleSQLiteError(connection); -} - -SQLiteInstance::SQLiteInstance(sqlite3 * connection) - : m_connection(connection) -{ -} - -SQLiteInstance::~SQLiteInstance() -{ - int result = sqlite3_close(m_connection); - checkSQLiteError(m_connection, result); -} - -SQLiteStatementPtr SQLiteInstance::prepare(const std::string & sql_text) -{ - sqlite3_stmt * statement; - int result = sqlite3_prepare_v2(m_connection, sql_text.data(), static_cast(sql_text.size()), &statement, nullptr); - - if(result == SQLITE_OK) - return SQLiteStatementPtr(new SQLiteStatement(*this, statement)); - - sqlite3_finalize(statement); - handleSQLiteError(m_connection); -} +/* + * SQLiteConnection.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "SQLiteConnection.h" + +#include + +[[noreturn]] static void handleSQLiteError(sqlite3 * connection) +{ + const char * message = sqlite3_errmsg(connection); + throw std::runtime_error(std::string("SQLite error: ") + message); +} + +static void checkSQLiteError(sqlite3 * connection, int result) +{ + if(result != SQLITE_OK) + handleSQLiteError(connection); +} + +SQLiteStatement::SQLiteStatement(SQLiteInstance & instance, sqlite3_stmt * statement) + : m_instance(instance) + , m_statement(statement) +{ +} + +SQLiteStatement::~SQLiteStatement() +{ + int result = sqlite3_finalize(m_statement); + checkSQLiteError(m_instance.m_connection, result); +} + +bool SQLiteStatement::execute() +{ + int result = sqlite3_step(m_statement); + + switch(result) + { + case SQLITE_DONE: + return false; + case SQLITE_ROW: + return true; + default: + checkSQLiteError(m_instance.m_connection, result); + return false; + } +} + +void SQLiteStatement::reset() +{ + int result = sqlite3_reset(m_statement); + checkSQLiteError(m_instance.m_connection, result); +} + +void SQLiteStatement::clear() +{ + int result = sqlite3_clear_bindings(m_statement); + checkSQLiteError(m_instance.m_connection, result); +} + +void SQLiteStatement::setBindSingle(size_t index, const double & value) +{ + int result = sqlite3_bind_double(m_statement, static_cast(index), value); + checkSQLiteError(m_instance.m_connection, result); +} + +void SQLiteStatement::setBindSingle(size_t index, const bool & value) +{ + int result = sqlite3_bind_int(m_statement, static_cast(value), value); + checkSQLiteError(m_instance.m_connection, result); +} + +void SQLiteStatement::setBindSingle(size_t index, const uint8_t & value) +{ + int result = sqlite3_bind_int(m_statement, static_cast(index), value); + checkSQLiteError(m_instance.m_connection, result); +} + +void SQLiteStatement::setBindSingle(size_t index, const uint16_t & value) +{ + int result = sqlite3_bind_int(m_statement, static_cast(index), value); + checkSQLiteError(m_instance.m_connection, result); +} +void SQLiteStatement::setBindSingle(size_t index, const uint32_t & value) +{ + int result = sqlite3_bind_int(m_statement, static_cast(index), value); + checkSQLiteError(m_instance.m_connection, result); +} + +void SQLiteStatement::setBindSingle(size_t index, const int32_t & value) +{ + int result = sqlite3_bind_int(m_statement, static_cast(index), value); + checkSQLiteError(m_instance.m_connection, result); +} + +void SQLiteStatement::setBindSingle(size_t index, const int64_t & value) +{ + int result = sqlite3_bind_int64(m_statement, static_cast(index), value); + checkSQLiteError(m_instance.m_connection, result); +} + +void SQLiteStatement::setBindSingle(size_t index, const std::string & value) +{ + int result = sqlite3_bind_text(m_statement, static_cast(index), value.data(), static_cast(value.size()), SQLITE_STATIC); + checkSQLiteError(m_instance.m_connection, result); +} + +void SQLiteStatement::setBindSingle(size_t index, const char * value) +{ + int result = sqlite3_bind_text(m_statement, static_cast(index), value, -1, SQLITE_STATIC); + checkSQLiteError(m_instance.m_connection, result); +} + +void SQLiteStatement::getColumnSingle(size_t index, double & value) +{ + value = sqlite3_column_double(m_statement, static_cast(index)); +} + +void SQLiteStatement::getColumnSingle(size_t index, bool & value) +{ + value = sqlite3_column_int(m_statement, static_cast(index)) != 0; +} + +void SQLiteStatement::getColumnSingle(size_t index, uint8_t & value) +{ + value = static_cast(sqlite3_column_int(m_statement, static_cast(index))); +} + +void SQLiteStatement::getColumnSingle(size_t index, uint16_t & value) +{ + value = static_cast(sqlite3_column_int(m_statement, static_cast(index))); +} + +void SQLiteStatement::getColumnSingle(size_t index, int32_t & value) +{ + value = sqlite3_column_int(m_statement, static_cast(index)); +} + +void SQLiteStatement::getColumnSingle(size_t index, uint32_t & value) +{ + value = sqlite3_column_int(m_statement, static_cast(index)); +} + +void SQLiteStatement::getColumnSingle(size_t index, int64_t & value) +{ + value = sqlite3_column_int64(m_statement, static_cast(index)); +} + +void SQLiteStatement::getColumnSingle(size_t index, std::string & value) +{ + const auto * value_raw = sqlite3_column_text(m_statement, static_cast(index)); + value = reinterpret_cast(value_raw); +} + +SQLiteInstancePtr SQLiteInstance::open(const boost::filesystem::path & db_path, bool allow_write) +{ + int flags = allow_write ? (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) : SQLITE_OPEN_READONLY; + + sqlite3 * connection; + int result = sqlite3_open_v2(db_path.c_str(), &connection, flags, nullptr); + + if(result == SQLITE_OK) + return SQLiteInstancePtr(new SQLiteInstance(connection)); + + sqlite3_close(connection); + handleSQLiteError(connection); +} + +SQLiteInstance::SQLiteInstance(sqlite3 * connection) + : m_connection(connection) +{ +} + +SQLiteInstance::~SQLiteInstance() +{ + int result = sqlite3_close(m_connection); + checkSQLiteError(m_connection, result); +} + +SQLiteStatementPtr SQLiteInstance::prepare(const std::string & sql_text) +{ + sqlite3_stmt * statement; + int result = sqlite3_prepare_v2(m_connection, sql_text.data(), static_cast(sql_text.size()), &statement, nullptr); + + if(result == SQLITE_OK) + return SQLiteStatementPtr(new SQLiteStatement(*this, statement)); + + sqlite3_finalize(statement); + handleSQLiteError(m_connection); +} diff --git a/lobby/SQLiteConnection.h b/lobby/SQLiteConnection.h index 268b1705e..ac52529ca 100644 --- a/lobby/SQLiteConnection.h +++ b/lobby/SQLiteConnection.h @@ -1,115 +1,115 @@ -/* - * SQLiteConnection.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -typedef struct sqlite3 sqlite3; -typedef struct sqlite3_stmt sqlite3_stmt; - -class SQLiteInstance; -class SQLiteStatement; - -using SQLiteInstancePtr = std::unique_ptr; -using SQLiteStatementPtr = std::unique_ptr; - -class SQLiteStatement : boost::noncopyable -{ -public: - friend class SQLiteInstance; - - bool execute(); - void reset(); - void clear(); - - ~SQLiteStatement(); - - template - void executeOnce(const Args &... args) - { - setBinds(args...); - execute(); - reset(); - } - - template - void setBinds(const Args &... args) - { - setBindSingle(1, args...); // The leftmost SQL parameter has an index of 1 - } - - template - void getColumns(Args &... args) - { - getColumnSingle(0, args...); // The leftmost column of the result set has the index 0 - } - -private: - void setBindSingle(size_t index, const double & value); - void setBindSingle(size_t index, const bool & value); - void setBindSingle(size_t index, const uint8_t & value); - void setBindSingle(size_t index, const uint16_t & value); - void setBindSingle(size_t index, const uint32_t & value); - void setBindSingle(size_t index, const int32_t & value); - void setBindSingle(size_t index, const int64_t & value); - void setBindSingle(size_t index, const std::string & value); - void setBindSingle(size_t index, const char * value); - - void getColumnSingle(size_t index, double & value); - void getColumnSingle(size_t index, bool & value); - void getColumnSingle(size_t index, uint8_t & value); - void getColumnSingle(size_t index, uint16_t & value); - void getColumnSingle(size_t index, uint32_t & value); - void getColumnSingle(size_t index, int32_t & value); - void getColumnSingle(size_t index, int64_t & value); - void getColumnSingle(size_t index, std::string & value); - - template - void getColumnSingle(size_t index, std::chrono::duration & value) - { - int64_t durationValue = 0; - getColumnSingle(index, durationValue); - value = std::chrono::duration(durationValue); - } - - SQLiteStatement(SQLiteInstance & instance, sqlite3_stmt * statement); - - template - void setBindSingle(size_t index, T const & arg, const Args &... args) - { - setBindSingle(index, arg); - setBindSingle(index + 1, args...); - } - - template - void getColumnSingle(size_t index, T & arg, Args &... args) - { - getColumnSingle(index, arg); - getColumnSingle(index + 1, args...); - } - - SQLiteInstance & m_instance; - sqlite3_stmt * m_statement; -}; - -class SQLiteInstance : boost::noncopyable -{ -public: - friend class SQLiteStatement; - - static SQLiteInstancePtr open(const boost::filesystem::path & db_path, bool allow_write); - - ~SQLiteInstance(); - - SQLiteStatementPtr prepare(const std::string & statement); - -private: - SQLiteInstance(sqlite3 * connection); - - sqlite3 * m_connection; -}; +/* + * SQLiteConnection.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +typedef struct sqlite3 sqlite3; +typedef struct sqlite3_stmt sqlite3_stmt; + +class SQLiteInstance; +class SQLiteStatement; + +using SQLiteInstancePtr = std::unique_ptr; +using SQLiteStatementPtr = std::unique_ptr; + +class SQLiteStatement : boost::noncopyable +{ +public: + friend class SQLiteInstance; + + bool execute(); + void reset(); + void clear(); + + ~SQLiteStatement(); + + template + void executeOnce(const Args &... args) + { + setBinds(args...); + execute(); + reset(); + } + + template + void setBinds(const Args &... args) + { + setBindSingle(1, args...); // The leftmost SQL parameter has an index of 1 + } + + template + void getColumns(Args &... args) + { + getColumnSingle(0, args...); // The leftmost column of the result set has the index 0 + } + +private: + void setBindSingle(size_t index, const double & value); + void setBindSingle(size_t index, const bool & value); + void setBindSingle(size_t index, const uint8_t & value); + void setBindSingle(size_t index, const uint16_t & value); + void setBindSingle(size_t index, const uint32_t & value); + void setBindSingle(size_t index, const int32_t & value); + void setBindSingle(size_t index, const int64_t & value); + void setBindSingle(size_t index, const std::string & value); + void setBindSingle(size_t index, const char * value); + + void getColumnSingle(size_t index, double & value); + void getColumnSingle(size_t index, bool & value); + void getColumnSingle(size_t index, uint8_t & value); + void getColumnSingle(size_t index, uint16_t & value); + void getColumnSingle(size_t index, uint32_t & value); + void getColumnSingle(size_t index, int32_t & value); + void getColumnSingle(size_t index, int64_t & value); + void getColumnSingle(size_t index, std::string & value); + + template + void getColumnSingle(size_t index, std::chrono::duration & value) + { + int64_t durationValue = 0; + getColumnSingle(index, durationValue); + value = std::chrono::duration(durationValue); + } + + SQLiteStatement(SQLiteInstance & instance, sqlite3_stmt * statement); + + template + void setBindSingle(size_t index, T const & arg, const Args &... args) + { + setBindSingle(index, arg); + setBindSingle(index + 1, args...); + } + + template + void getColumnSingle(size_t index, T & arg, Args &... args) + { + getColumnSingle(index, arg); + getColumnSingle(index + 1, args...); + } + + SQLiteInstance & m_instance; + sqlite3_stmt * m_statement; +}; + +class SQLiteInstance : boost::noncopyable +{ +public: + friend class SQLiteStatement; + + static SQLiteInstancePtr open(const boost::filesystem::path & db_path, bool allow_write); + + ~SQLiteInstance(); + + SQLiteStatementPtr prepare(const std::string & statement); + +private: + SQLiteInstance(sqlite3 * connection); + + sqlite3 * m_connection; +}; From baa73f5433c3b59135777b1565a8e8eae542bf93 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 20 Jan 2024 00:26:25 +0200 Subject: [PATCH 37/98] Code cleanup --- client/CServerHandler.cpp | 22 +++++++++++----------- client/CServerHandler.h | 2 +- lib/network/NetworkConnection.cpp | 4 ++-- lib/network/NetworkServer.cpp | 4 ++-- lobby/SQLiteConnection.h | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index f6ce8a5c4..ce9495541 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -90,22 +90,22 @@ public: { boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); - T & ptr = static_cast(pack); + auto & ref = static_cast(pack); ApplyOnLobbyHandlerNetPackVisitor visitor(*handler); - logNetwork->trace("\tImmediately apply on lobby: %s", typeid(ptr).name()); - ptr.visit(visitor); + logNetwork->trace("\tImmediately apply on lobby: %s", typeid(ref).name()); + ref.visit(visitor); return visitor.getResult(); } void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override { - T & ptr = static_cast(pack); + auto & ref = static_cast(pack); ApplyOnLobbyScreenNetPackVisitor visitor(*handler, lobby); - logNetwork->trace("\tApply on lobby from queue: %s", typeid(ptr).name()); - ptr.visit(visitor); + logNetwork->trace("\tApply on lobby from queue: %s", typeid(ref).name()); + ref.visit(visitor); } }; @@ -260,11 +260,11 @@ void CServerHandler::connectToServer(const std::string & addr, const ui16 port) if (!isServerLocal()) { - Settings serverAddress = settings.write["server"]["remoteHostname"]; - serverAddress->String() = addr; + Settings remoteAddress = settings.write["server"]["remoteHostname"]; + remoteAddress->String() = addr; - Settings serverPort = settings.write["server"]["remotePort"]; - serverPort->Integer() = port; + Settings remotePort = settings.write["server"]["remotePort"]; + remotePort->Integer() = port; } networkHandler->connectToRemote(*this, addr, port); @@ -312,7 +312,7 @@ void CServerHandler::onConnectionEstablished(const std::shared_ptrgetApplier(CTypeList::getInstance().getTypeID(&pack)); //find the applier + const CBaseForLobbyApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(&pack)); //find the applier apply->applyOnLobbyScreen(dynamic_cast(SEL), this, pack); GH.windows().totalRedraw(); } diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 0a5999c0e..39be961df 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -82,7 +82,7 @@ public: }; /// structure to handle running server and connecting to it -class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClientListener, public INetworkTimerListener, boost::noncopyable +class CServerHandler final : public IServerAPI, public LobbyInfo, public INetworkClientListener, public INetworkTimerListener, boost::noncopyable { friend class ApplyOnLobbyHandlerNetPackVisitor; diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index b2fce0160..f4ae146e2 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -26,7 +26,7 @@ void NetworkConnection::start() boost::asio::async_read(*socket, readBuffer, boost::asio::transfer_exactly(messageHeaderSize), - std::bind(&NetworkConnection::onHeaderReceived,this, _1)); + [this](const auto & ec, const auto & endpoint) { onHeaderReceived(ec); }); } void NetworkConnection::onHeaderReceived(const boost::system::error_code & ec) @@ -42,7 +42,7 @@ void NetworkConnection::onHeaderReceived(const boost::system::error_code & ec) boost::asio::async_read(*socket, readBuffer, boost::asio::transfer_exactly(messageSize), - std::bind(&NetworkConnection::onPacketReceived,this, _1, messageSize)); + [this, messageSize](const auto & ec, const auto & endpoint) { onPacketReceived(ec, messageSize); }); } uint32_t NetworkConnection::readPacketSize() diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp index 546d0b652..39cbd29bf 100644 --- a/lib/network/NetworkServer.cpp +++ b/lib/network/NetworkServer.cpp @@ -14,8 +14,8 @@ VCMI_LIB_NAMESPACE_BEGIN NetworkServer::NetworkServer(INetworkServerListener & listener, const std::shared_ptr & context) - : listener(listener) - , io(context) + : io(context) + , listener(listener) { } diff --git a/lobby/SQLiteConnection.h b/lobby/SQLiteConnection.h index ac52529ca..f501c5c83 100644 --- a/lobby/SQLiteConnection.h +++ b/lobby/SQLiteConnection.h @@ -9,8 +9,8 @@ */ #pragma once -typedef struct sqlite3 sqlite3; -typedef struct sqlite3_stmt sqlite3_stmt; +using sqlite3 = struct sqlite3; +using sqlite3_stmt = struct sqlite3_stmt; class SQLiteInstance; class SQLiteStatement; From 9e62eb28c5f8d1e5efe1425c7d277e4302a0b221 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 21 Jan 2024 00:53:22 +0200 Subject: [PATCH 38/98] Fix merge --- client/CMT.cpp | 3 +-- client/CServerHandler.cpp | 6 ++---- server/NetPacksLobbyServer.cpp | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index 254b8b37c..64ba2cea8 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -138,8 +138,7 @@ int main(int argc, char * argv[]) ("nointro,i", "skips intro movies") ("donotstartserver,d","do not attempt to start server and just connect to it instead server") ("serverport", po::value(), "override port specified in config file") - ("savefrequency", po::value(), "limit auto save creation to each N days") - ("uuid", po::value(), "uuid for the client"); + ("savefrequency", po::value(), "limit auto save creation to each N days"); if(argc > 1) { diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 5a6369dde..10ac7e48e 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -277,9 +277,6 @@ void CServerHandler::onConnectionFailed(const std::string & errorMessage) // retry - local server might be still starting up logNetwork->debug("\nCannot establish connection. %s. Retrying...", errorMessage); networkHandler->createTimer(*this, std::chrono::milliseconds(100)); - - nextClient = std::make_unique(); - c->setCallback(nextClient.get()); } else { @@ -307,8 +304,10 @@ void CServerHandler::onConnectionEstablished(const std::shared_ptrinfo("Connection established"); c = std::make_shared(netConnection); + nextClient = std::make_unique(); c->uuid = uuid; c->enterLobbyConnectionMode(); + c->setCallback(nextClient.get()); sendClientConnecting(); } @@ -325,7 +324,6 @@ std::set CServerHandler::getHumanColors() return clientHumanColors(c->connectionID); } - PlayerColor CServerHandler::myFirstColor() const { return clientFirstColor(c->connectionID); diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index a3610aace..e83d54bb8 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -92,7 +92,7 @@ void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientDisconnected(LobbyC return; } - if(pack.c->uuid != srv.cmdLineOptions["uuid"].as()) + if(pack.c->connectionID != srv.hostClientId) { result = false; return; From 388ca6e776d91a279c590b79e47b7cca8f040cbe Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 21 Jan 2024 16:48:36 +0200 Subject: [PATCH 39/98] Added list of active accounts and rooms to UI. Added room creation logic --- client/CMakeLists.txt | 1 + client/CServerHandler.cpp | 29 +-- client/CServerHandler.h | 11 +- client/NetPacksLobbyClient.cpp | 16 +- client/eventsSDL/InputSourceKeyboard.cpp | 10 +- client/globalLobby/GlobalLobbyClient.cpp | 98 +++++++- client/globalLobby/GlobalLobbyClient.h | 21 +- client/globalLobby/GlobalLobbyDefines.h | 28 +++ client/globalLobby/GlobalLobbyLoginWindow.cpp | 2 +- client/globalLobby/GlobalLobbyServerSetup.cpp | 14 +- client/globalLobby/GlobalLobbyWidget.cpp | 107 ++++++++ client/globalLobby/GlobalLobbyWidget.h | 35 +++ client/globalLobby/GlobalLobbyWindow.cpp | 21 ++ client/globalLobby/GlobalLobbyWindow.h | 7 + client/lobby/SelectionTab.cpp | 2 +- client/mainmenu/CMainMenu.cpp | 28 +-- client/mainmenu/CMainMenu.h | 6 +- client/windows/CMapOverview.h | 2 +- config/widgets/lobbyWindow.json | 32 ++- lib/StartInfo.cpp | 2 +- lib/StartInfo.h | 14 +- lib/gameState/CGameState.cpp | 6 +- lib/gameState/CGameStateCampaign.cpp | 2 +- lib/network/NetworkConnection.cpp | 5 +- lib/networkPacks/PacksForLobby.h | 2 +- lib/serializer/Connection.cpp | 5 + lib/serializer/Connection.h | 1 + lobby/LobbyDatabase.cpp | 39 ++- lobby/LobbyDatabase.h | 5 +- lobby/LobbyDefines.h | 16 +- lobby/LobbyServer.cpp | 24 +- server/CMakeLists.txt | 1 + server/CVCMIServer.cpp | 228 +++--------------- server/CVCMIServer.h | 6 +- server/EntryPoint.cpp | 170 +++++++++++++ server/GlobalLobbyProcessor.cpp | 4 +- server/NetPacksLobbyServer.cpp | 2 +- 37 files changed, 698 insertions(+), 304 deletions(-) create mode 100644 client/globalLobby/GlobalLobbyDefines.h create mode 100644 server/EntryPoint.cpp diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 507f6012b..bd5791476 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -277,6 +277,7 @@ set(client_HEADERS renderSDL/SDL_PixelAccess.h globalLobby/GlobalLobbyClient.h + globalLobby/GlobalLobbyDefines.h globalLobby/GlobalLobbyLoginWindow.h globalLobby/GlobalLobbyServerSetup.h globalLobby/GlobalLobbyWidget.h diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 10ac7e48e..35c71ff81 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -126,9 +126,6 @@ public: } }; -static const std::string NAME_AFFIX = "client"; -static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name - CServerHandler::~CServerHandler() { networkHandler->stop(); @@ -141,7 +138,8 @@ CServerHandler::CServerHandler() , applier(std::make_unique>()) , lobbyClient(std::make_unique()) , client(nullptr) - , loadMode(0) + , loadMode(ELoadMode::NONE) + , screenType(ESelectionScreen::unknown) , campaignStateToSend(nullptr) , campaignServerRestartLock(false) { @@ -158,7 +156,7 @@ void CServerHandler::threadRunNetwork() logGlobal->info("Ending network thread"); } -void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::vector * names) +void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen, const std::vector & names) { hostClientId = -1; state = EClientState::NONE; @@ -169,9 +167,10 @@ void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std:: playerNames.clear(); si->difficulty = 1; si->mode = mode; + screenType = screen; myNames.clear(); - if(names && !names->empty()) //if have custom set of player names - use it - myNames = *names; + if(!names.empty()) //if have custom set of player names - use it + myNames = names; else myNames.push_back(settings["general"]["playerName"].String()); } @@ -617,7 +616,7 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const if(client) { lsg.initializedStartInfo = std::make_shared(* const_cast(client->getStartInfo(true))); - lsg.initializedStartInfo->mode = StartInfo::NEW_GAME; + lsg.initializedStartInfo->mode = EStartMode::NEW_GAME; lsg.initializedStartInfo->seedToBeUsed = lsg.initializedStartInfo->seedPostInit = 0; * si = * lsg.initializedStartInfo; } @@ -641,13 +640,13 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta switch(si->mode) { - case StartInfo::NEW_GAME: + case EStartMode::NEW_GAME: client->newGame(gameState); break; - case StartInfo::CAMPAIGN: + case EStartMode::CAMPAIGN: client->newGame(gameState); break; - case StartInfo::LOAD_GAME: + case EStartMode::LOAD_GAME: client->loadGame(gameState); break; default: @@ -765,7 +764,7 @@ int CServerHandler::howManyPlayerInterfaces() return playerInts; } -ui8 CServerHandler::getLoadMode() +ELoadMode CServerHandler::getLoadMode() { if(loadMode != ELoadMode::TUTORIAL && state == EClientState::GAMEPLAY) { @@ -790,15 +789,13 @@ void CServerHandler::debugStartTest(std::string filename, bool save) auto mapInfo = std::make_shared(); if(save) { - resetStateForLobby(StartInfo::LOAD_GAME); + resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, {}); mapInfo->saveInit(ResourcePath(filename, EResType::SAVEGAME)); - screenType = ESelectionScreen::loadGame; } else { - resetStateForLobby(StartInfo::NEW_GAME); + resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, {}); mapInfo->mapInit(filename); - screenType = ESelectionScreen::newGame; } if(settings["session"]["donotstartserver"].Bool()) connectToServer(getLocalHostname(), getLocalPort()); diff --git a/client/CServerHandler.h b/client/CServerHandler.h index f86f8eaaa..9733887cb 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -40,6 +40,9 @@ class GlobalLobbyClient; class HighScoreCalculation; class HighScoreParameter; +enum class ESelectionScreen : ui8; +enum class ELoadMode : ui8; + // TODO: Add mutex so we can't set CONNECTION_CANCELLED if client already connected, but thread not setup yet enum class EClientState : ui8 { @@ -128,8 +131,8 @@ public: // For starting non-custom campaign and continue to next mission std::shared_ptr campaignStateToSend; - ui8 screenType; // To create lobby UI only after server is setup - ui8 loadMode; // For saves filtering in SelectionTab + ESelectionScreen screenType; // To create lobby UI only after server is setup + ELoadMode loadMode; // For saves filtering in SelectionTab //////////////////// std::unique_ptr th; @@ -143,7 +146,7 @@ public: CServerHandler(); ~CServerHandler(); - void resetStateForLobby(const StartInfo::EMode mode, const std::vector * names = nullptr); + void resetStateForLobby(EStartMode mode, ESelectionScreen screen, const std::vector & names); void startLocalServerAndConnect(bool connectToLobby); void connectToServer(const std::string & addr, const ui16 port); @@ -196,7 +199,7 @@ public: // TODO: LobbyState must be updated within game so we should always know how many player interfaces our client handle int howManyPlayerInterfaces(); - ui8 getLoadMode(); + ELoadMode getLoadMode(); void visitForLobby(CPackForLobby & lobbyPack); void visitForClient(CPackForClient & clientPack); diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index 578e28a1c..b7045dea7 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -20,6 +20,8 @@ #include "lobby/SelectionTab.h" #include "lobby/CBonusSelection.h" #include "globalLobby/GlobalLobbyWindow.h" +#include "globalLobby/GlobalLobbyServerSetup.h" +#include "globalLobby/GlobalLobbyClient.h" #include "CServerHandler.h" #include "CGameInfo.h" @@ -49,6 +51,18 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon if (GH.windows().topWindow()) GH.windows().popWindows(1); + if (!GH.windows().findWindows().empty()) + { + // announce opened game room + // TODO: find better approach? + int roomType = settings["lobby"]["roomType"].Integer(); + + if (roomType != 0) + handler.getGlobalLobby().sendOpenPrivateRoom(); + else + handler.getGlobalLobby().sendOpenPublicRoom(); + } + while (!GH.windows().findWindows().empty()) { // if global lobby is open, pop all dialogs on top of it as well as lobby itself @@ -141,7 +155,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pac } handler.state = EClientState::STARTING; - if(handler.si->mode != StartInfo::LOAD_GAME || pack.clientId == handler.c->connectionID) + if(handler.si->mode != EStartMode::LOAD_GAME || pack.clientId == handler.c->connectionID) { auto modeBackup = handler.si->mode; handler.si = pack.initializedStartInfo; diff --git a/client/eventsSDL/InputSourceKeyboard.cpp b/client/eventsSDL/InputSourceKeyboard.cpp index 01b7c990e..0901dc420 100644 --- a/client/eventsSDL/InputSourceKeyboard.cpp +++ b/client/eventsSDL/InputSourceKeyboard.cpp @@ -16,6 +16,8 @@ #include "../gui/CGuiHandler.h" #include "../gui/EventDispatcher.h" #include "../gui/ShortcutHandler.h" +#include "../CServerHandler.h" +#include "../globalLobby/GlobalLobbyClient.h" #include #include @@ -31,6 +33,8 @@ InputSourceKeyboard::InputSourceKeyboard() void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key) { + assert(key.state == SDL_PRESSED); + if (SDL_IsTextInputActive() == SDL_TRUE) { if(key.keysym.sym == SDLK_v && isKeyboardCtrlDown()) @@ -51,7 +55,11 @@ void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key) return; // ignore periodic event resends } - assert(key.state == SDL_PRESSED); + + if(key.keysym.sym == SDLK_TAB && isKeyboardCtrlDown()) + { + CSH->getGlobalLobby().activateInterface(); + } if(key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool()) { diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index 66877ef82..0a0b215cb 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -59,6 +59,12 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptronActiveAccounts(activeAccounts); +} + +void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json) +{ + activeRooms.clear(); + + for (auto const & jsonEntry : json["gameRooms"].Vector()) + { + GlobalLobbyRoom room; + + room.gameRoomID = jsonEntry["gameRoomID"].String(); + room.hostAccountID = jsonEntry["hostAccountID"].String(); + room.hostAccountDisplayName = jsonEntry["hostAccountDisplayName"].String(); + room.description = jsonEntry["description"].String(); +// room.status = jsonEntry["status"].String(); + room.playersCount = jsonEntry["playersCount"].Integer(); + room.playersLimit = jsonEntry["playersLimit"].Integer(); + + activeRooms.push_back(room); + } + + auto lobbyWindowPtr = lobbyWindow.lock(); + if(lobbyWindowPtr) + lobbyWindowPtr->onActiveRooms(activeRooms); +} + +void GlobalLobbyClient::receiveJoinRoomSuccess(const JsonNode & json) +{ + // TODO: store "gameRoomID" field and use it for future queries } void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr & connection) @@ -211,6 +257,24 @@ void GlobalLobbyClient::sendMessage(const JsonNode & data) networkConnection->sendPacket(payloadBuffer); } +void GlobalLobbyClient::sendOpenPublicRoom() +{ + JsonNode toSend; + toSend["type"].String() = "openGameRoom"; + toSend["hostAccountID"] = settings["lobby"]["accountID"]; + toSend["roomType"].String() = "public"; + sendMessage(toSend); +} + +void GlobalLobbyClient::sendOpenPrivateRoom() +{ + JsonNode toSend; + toSend["type"].String() = "openGameRoom"; + toSend["hostAccountID"] = settings["lobby"]["accountID"]; + toSend["roomType"].String() = "private"; + sendMessage(toSend); +} + void GlobalLobbyClient::connect() { std::string hostname = settings["lobby"]["hostname"].String(); @@ -246,3 +310,27 @@ std::shared_ptr GlobalLobbyClient::createLobbyWindow() lobbyWindowLock = lobbyWindowPtr; return lobbyWindowPtr; } + +const std::vector & GlobalLobbyClient::getActiveAccounts() const +{ + return activeAccounts; +} + +const std::vector & GlobalLobbyClient::getActiveRooms() const +{ + return activeRooms; +} + +void GlobalLobbyClient::activateInterface() +{ + if (!GH.windows().findWindows().empty()) + return; + + if (!GH.windows().findWindows().empty()) + return; + + if (isConnected()) + GH.windows().pushWindow(createLobbyWindow()); + else + GH.windows().pushWindow(createLoginWindow()); +} diff --git a/client/globalLobby/GlobalLobbyClient.h b/client/globalLobby/GlobalLobbyClient.h index ca54c0535..c6a80fa8a 100644 --- a/client/globalLobby/GlobalLobbyClient.h +++ b/client/globalLobby/GlobalLobbyClient.h @@ -9,6 +9,7 @@ */ #pragma once +#include "GlobalLobbyDefines.h" #include "../../lib/network/NetworkInterface.h" VCMI_LIB_NAMESPACE_BEGIN @@ -18,8 +19,11 @@ VCMI_LIB_NAMESPACE_END class GlobalLobbyLoginWindow; class GlobalLobbyWindow; -class GlobalLobbyClient : public INetworkClientListener, boost::noncopyable +class GlobalLobbyClient final : public INetworkClientListener, boost::noncopyable { + std::vector activeAccounts; + std::vector activeRooms; + std::shared_ptr networkConnection; std::weak_ptr loginWindow; @@ -40,14 +44,25 @@ class GlobalLobbyClient : public INetworkClientListener, boost::noncopyable void receiveChatHistory(const JsonNode & json); void receiveChatMessage(const JsonNode & json); void receiveActiveAccounts(const JsonNode & json); + void receiveActiveGameRooms(const JsonNode & json); + void receiveJoinRoomSuccess(const JsonNode & json); + + std::shared_ptr createLoginWindow(); + std::shared_ptr createLobbyWindow(); public: explicit GlobalLobbyClient(); ~GlobalLobbyClient(); + const std::vector & getActiveAccounts() const; + const std::vector & getActiveRooms() const; + + /// Activate interface and pushes lobby UI as top window + void activateInterface(); void sendMessage(const JsonNode & data); + void sendOpenPublicRoom(); + void sendOpenPrivateRoom(); + void connect(); bool isConnected(); - std::shared_ptr createLoginWindow(); - std::shared_ptr createLobbyWindow(); }; diff --git a/client/globalLobby/GlobalLobbyDefines.h b/client/globalLobby/GlobalLobbyDefines.h new file mode 100644 index 000000000..83c79c045 --- /dev/null +++ b/client/globalLobby/GlobalLobbyDefines.h @@ -0,0 +1,28 @@ +/* + * GlobalLobbyClient.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +struct GlobalLobbyAccount +{ + std::string accountID; + std::string displayName; + std::string status; +}; + +struct GlobalLobbyRoom +{ + std::string gameRoomID; + std::string hostAccountID; + std::string hostAccountDisplayName; + std::string description; +// std::string status; + int playersCount; + int playersLimit; +}; diff --git a/client/globalLobby/GlobalLobbyLoginWindow.cpp b/client/globalLobby/GlobalLobbyLoginWindow.cpp index adb8244b0..bf7a83753 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.cpp +++ b/client/globalLobby/GlobalLobbyLoginWindow.cpp @@ -74,7 +74,7 @@ void GlobalLobbyLoginWindow::onLogin() void GlobalLobbyLoginWindow::onConnectionSuccess() { close(); - GH.windows().pushWindow(CSH->getGlobalLobby().createLobbyWindow()); + CSH->getGlobalLobby().activateInterface(); } void GlobalLobbyLoginWindow::onConnectionFailed(const std::string & reason) diff --git a/client/globalLobby/GlobalLobbyServerSetup.cpp b/client/globalLobby/GlobalLobbyServerSetup.cpp index 9a930932f..5a5fa6f05 100644 --- a/client/globalLobby/GlobalLobbyServerSetup.cpp +++ b/client/globalLobby/GlobalLobbyServerSetup.cpp @@ -125,17 +125,15 @@ void GlobalLobbyServerSetup::onGameModeChanged(int value) void GlobalLobbyServerSetup::onCreate() { if(toggleGameMode->getSelected() == 0) - { - CSH->resetStateForLobby(StartInfo::NEW_GAME, nullptr); - CSH->screenType = ESelectionScreen::newGame; - } + CSH->resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, {}); else - { - CSH->resetStateForLobby(StartInfo::LOAD_GAME, nullptr); - CSH->screenType = ESelectionScreen::loadGame; - } + CSH->resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, {}); + CSH->loadMode = ELoadMode::MULTI; CSH->startLocalServerAndConnect(true); + + buttonCreate->block(true); + buttonClose->block(true); } void GlobalLobbyServerSetup::onClose() diff --git a/client/globalLobby/GlobalLobbyWidget.cpp b/client/globalLobby/GlobalLobbyWidget.cpp index e5b462721..c7bb40e6a 100644 --- a/client/globalLobby/GlobalLobbyWidget.cpp +++ b/client/globalLobby/GlobalLobbyWidget.cpp @@ -10,12 +10,19 @@ #include "StdInc.h" #include "GlobalLobbyWidget.h" + +#include "GlobalLobbyClient.h" #include "GlobalLobbyWindow.h" +#include "../CServerHandler.h" #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" +#include "../widgets/Buttons.h" +#include "../widgets/MiscWidgets.h" +#include "../widgets/ObjectLists.h" #include "../widgets/TextControls.h" +#include "../../lib/MetaString.h" GlobalLobbyWidget::GlobalLobbyWidget(GlobalLobbyWindow * window) : window(window) { @@ -23,10 +30,59 @@ GlobalLobbyWidget::GlobalLobbyWidget(GlobalLobbyWindow * window) addCallback("sendMessage", [this](int) { this->window->doSendChatMessage(); }); addCallback("createGameRoom", [this](int) { this->window->doCreateGameRoom(); }); + REGISTER_BUILDER("accountList", &GlobalLobbyWidget::buildAccountList); + REGISTER_BUILDER("roomList", &GlobalLobbyWidget::buildRoomList); + const JsonNode config(JsonPath::builtin("config/widgets/lobbyWindow.json")); build(config); } +std::shared_ptr GlobalLobbyWidget::buildAccountList(const JsonNode & config) const +{ + const auto & createCallback = [this](size_t index) -> std::shared_ptr + { + const auto & accounts = CSH->getGlobalLobby().getActiveAccounts(); + + if(index < accounts.size()) + return std::make_shared(this->window, accounts[index]); + return std::make_shared(); + }; + + auto position = readPosition(config["position"]); + auto itemOffset = readPosition(config["itemOffset"]); + auto sliderPosition = readPosition(config["sliderPosition"]); + auto sliderSize = readPosition(config["sliderSize"]); + size_t visibleSize = 4; // FIXME: how many items can fit into UI? + size_t totalSize = 4; //FIXME: how many items are there in total + int sliderMode = 1 | 4; // present, vertical, blue + int initialPos = 0; + + return std::make_shared(createCallback, position, itemOffset, visibleSize, totalSize, initialPos, sliderMode, Rect(sliderPosition, sliderSize) ); +} + +std::shared_ptr GlobalLobbyWidget::buildRoomList(const JsonNode & config) const +{ + const auto & createCallback = [this](size_t index) -> std::shared_ptr + { + const auto & rooms = CSH->getGlobalLobby().getActiveRooms(); + + if(index < rooms.size()) + return std::make_shared(this->window, rooms[index]); + return std::make_shared(); + }; + + auto position = readPosition(config["position"]); + auto itemOffset = readPosition(config["itemOffset"]); + auto sliderPosition = readPosition(config["sliderPosition"]); + auto sliderSize = readPosition(config["sliderSize"]); + size_t visibleSize = 4; // FIXME: how many items can fit into UI? + size_t totalSize = 4; //FIXME: how many items are there in total + int sliderMode = 1 | 4; // present, vertical, blue + int initialPos = 0; + + return std::make_shared(createCallback, position, itemOffset, visibleSize, totalSize, initialPos, sliderMode, Rect(sliderPosition, sliderSize) ); +} + std::shared_ptr GlobalLobbyWidget::getAccountNameLabel() { return widget("accountNameLabel"); @@ -41,3 +97,54 @@ std::shared_ptr GlobalLobbyWidget::getGameChat() { return widget("gameChat"); } + +std::shared_ptr GlobalLobbyWidget::getAccountList() +{ + return widget("accountList"); +} + +std::shared_ptr GlobalLobbyWidget::getRoomList() +{ + return widget("roomList"); +} + +GlobalLobbyAccountCard::GlobalLobbyAccountCard(GlobalLobbyWindow * window, const GlobalLobbyAccount & accountDescription) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + + const auto & onInviteClicked = [window, accountID=accountDescription.accountID]() + { + window->doInviteAccount(accountID); + }; + + pos.w = 130; + pos.h = 40; + + backgroundOverlay = std::make_shared(Rect(0, 0, pos.w, pos.h), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64)); + labelName = std::make_shared( 5, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, accountDescription.displayName); + labelStatus = std::make_shared( 5, 20, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, accountDescription.status); + buttonInvite = std::make_shared(Point(95, 8), AnimationPath::builtin("settingsWindow/button32"), CButton::tooltip(), onInviteClicked); +} + +GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription) +{ + OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + + const auto & onJoinClicked = [window, roomID=roomDescription.gameRoomID]() + { + window->doJoinRoom(roomID); + }; + + auto roomSizeText = MetaString::createFromRawString("%d/%d"); + roomSizeText.replaceNumber(roomDescription.playersCount); + roomSizeText.replaceNumber(roomDescription.playersLimit); + + pos.w = 230; + pos.h = 40; + + backgroundOverlay = std::make_shared(Rect(0, 0, pos.w, pos.h), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64)); + labelName = std::make_shared( 5, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName); + labelStatus = std::make_shared( 5, 20, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, roomDescription.description); + labelRoomSize = std::make_shared( 160, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, roomSizeText.toString()); + buttonJoin = std::make_shared(Point(195, 8), AnimationPath::builtin("settingsWindow/button32"), CButton::tooltip(), onJoinClicked); +} diff --git a/client/globalLobby/GlobalLobbyWidget.h b/client/globalLobby/GlobalLobbyWidget.h index ed61dd314..4942add0a 100644 --- a/client/globalLobby/GlobalLobbyWidget.h +++ b/client/globalLobby/GlobalLobbyWidget.h @@ -12,15 +12,50 @@ #include "../gui/InterfaceObjectConfigurable.h" class GlobalLobbyWindow; +struct GlobalLobbyAccount; +struct GlobalLobbyRoom; +class CListBox; class GlobalLobbyWidget : public InterfaceObjectConfigurable { GlobalLobbyWindow * window; + std::shared_ptr buildAccountList(const JsonNode &) const; + std::shared_ptr buildRoomList(const JsonNode &) const; + public: GlobalLobbyWidget(GlobalLobbyWindow * window); std::shared_ptr getAccountNameLabel(); std::shared_ptr getMessageInput(); std::shared_ptr getGameChat(); + std::shared_ptr getAccountList(); + std::shared_ptr getRoomList(); +}; + +class GlobalLobbyAccountCard : public CIntObject +{ +public: + GlobalLobbyAccountCard(GlobalLobbyWindow * window, const GlobalLobbyAccount & accountDescription); + + GlobalLobbyWindow * window; + + std::shared_ptr backgroundOverlay; + std::shared_ptr labelName; + std::shared_ptr labelStatus; + std::shared_ptr buttonInvite; +}; + +class GlobalLobbyRoomCard : public CIntObject +{ +public: + GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription); + + GlobalLobbyWindow * window; + + std::shared_ptr backgroundOverlay; + std::shared_ptr labelName; + std::shared_ptr labelRoomSize; + std::shared_ptr labelStatus; + std::shared_ptr buttonJoin; }; diff --git a/client/globalLobby/GlobalLobbyWindow.cpp b/client/globalLobby/GlobalLobbyWindow.cpp index 11ce619b1..a4a9a67c3 100644 --- a/client/globalLobby/GlobalLobbyWindow.cpp +++ b/client/globalLobby/GlobalLobbyWindow.cpp @@ -19,6 +19,7 @@ #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" #include "../widgets/TextControls.h" +#include "../widgets/ObjectLists.h" #include "../../lib/CConfigHandler.h" #include "../../lib/MetaString.h" @@ -59,6 +60,16 @@ void GlobalLobbyWindow::doCreateGameRoom() // client requests to change room status to private or public } +void GlobalLobbyWindow::doInviteAccount(const std::string & accountID) +{ + +} + +void GlobalLobbyWindow::doJoinRoom(const std::string & roomID) +{ + +} + void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when) { MetaString chatMessageFormatted; @@ -71,3 +82,13 @@ void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std: widget->getGameChat()->setText(chatHistory); } + +void GlobalLobbyWindow::onActiveAccounts(const std::vector & accounts) +{ + widget->getAccountList()->reset(); +} + +void GlobalLobbyWindow::onActiveRooms(const std::vector & rooms) +{ + widget->getRoomList()->reset(); +} diff --git a/client/globalLobby/GlobalLobbyWindow.h b/client/globalLobby/GlobalLobbyWindow.h index d9d792f7e..e3c60a9d4 100644 --- a/client/globalLobby/GlobalLobbyWindow.h +++ b/client/globalLobby/GlobalLobbyWindow.h @@ -12,6 +12,8 @@ #include "../windows/CWindowObject.h" class GlobalLobbyWidget; +struct GlobalLobbyAccount; +struct GlobalLobbyRoom; class GlobalLobbyWindow : public CWindowObject { @@ -25,5 +27,10 @@ public: void doSendChatMessage(); void doCreateGameRoom(); + void doInviteAccount(const std::string & accountID); + void doJoinRoom(const std::string & roomID); + void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when); + void onActiveAccounts(const std::vector & accounts); + void onActiveRooms(const std::vector & rooms); }; diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 0e157458c..85e014c88 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -768,7 +768,7 @@ void SelectionTab::parseSaves(const std::unordered_set & files) mapInfo->saveInit(file); // Filter out other game modes - bool isCampaign = mapInfo->scenarioOptionsOfSave->mode == StartInfo::CAMPAIGN; + bool isCampaign = mapInfo->scenarioOptionsOfSave->mode == EStartMode::CAMPAIGN; bool isMultiplayer = mapInfo->amountOfHumanPlayersInSave > 1; bool isTutorial = boost::to_upper_copy(mapInfo->scenarioOptionsOfSave->mapname) == "MAPS/TUTORIAL"; switch(CSH->getLoadMode()) diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 911fe7f25..113b80c31 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -187,11 +187,11 @@ static std::function genCommand(CMenuScreen * menu, std::vector(ESelectionScreen::newGame); }; case 2: - return std::bind(CMainMenu::openLobby, ESelectionScreen::campaignList, true, nullptr, ELoadMode::NONE); + return []() { CMainMenu::openLobby(ESelectionScreen::campaignList, true, {}, ELoadMode::NONE);}; case 3: return std::bind(CMainMenu::startTutorial); } @@ -202,13 +202,14 @@ static std::function genCommand(CMenuScreen * menu, std::vector(ESelectionScreen::loadGame); }; case 2: - return std::bind(CMainMenu::openLobby, ESelectionScreen::loadGame, true, nullptr, ELoadMode::CAMPAIGN); + return []() { CMainMenu::openLobby(ESelectionScreen::loadGame, true, {}, ELoadMode::CAMPAIGN);}; case 3: - return std::bind(CMainMenu::openLobby, ESelectionScreen::loadGame, true, nullptr, ELoadMode::TUTORIAL); + return []() { CMainMenu::openLobby(ESelectionScreen::loadGame, true, {}, ELoadMode::TUTORIAL);}; + } } break; @@ -358,10 +359,9 @@ void CMainMenu::update() GH.windows().simpleRedraw(); } -void CMainMenu::openLobby(ESelectionScreen screenType, bool host, const std::vector * names, ELoadMode loadMode) +void CMainMenu::openLobby(ESelectionScreen screenType, bool host, const std::vector & names, ELoadMode loadMode) { - CSH->resetStateForLobby(screenType == ESelectionScreen::newGame ? StartInfo::NEW_GAME : StartInfo::LOAD_GAME, names); - CSH->screenType = screenType; + CSH->resetStateForLobby(screenType == ESelectionScreen::newGame ? EStartMode::NEW_GAME : EStartMode::LOAD_GAME, screenType, names); CSH->loadMode = loadMode; GH.windows().createAndPushWindow(host); @@ -376,8 +376,7 @@ void CMainMenu::openCampaignLobby(const std::string & campaignFileName, std::str void CMainMenu::openCampaignLobby(std::shared_ptr campaign) { - CSH->resetStateForLobby(StartInfo::CAMPAIGN); - CSH->screenType = ESelectionScreen::campaignList; + CSH->resetStateForLobby(EStartMode::CAMPAIGN, ESelectionScreen::campaignList, {}); CSH->campaignStateToSend = campaign; GH.windows().createAndPushWindow(); } @@ -420,7 +419,7 @@ void CMainMenu::startTutorial() auto mapInfo = std::make_shared(); mapInfo->mapInit(tutorialMap.getName()); - CMainMenu::openLobby(ESelectionScreen::newGame, true, nullptr, ELoadMode::NONE); + CMainMenu::openLobby(ESelectionScreen::newGame, true, {}, ELoadMode::NONE); CSH->startMapAfterConnection(mapInfo); } @@ -470,10 +469,7 @@ CMultiMode::CMultiMode(ESelectionScreen ScreenType) void CMultiMode::openLobby() { close(); - if (CSH->getGlobalLobby().isConnected()) - GH.windows().pushWindow(CSH->getGlobalLobby().createLobbyWindow()); - else - GH.windows().pushWindow(CSH->getGlobalLobby().createLoginWindow()); + CSH->getGlobalLobby().activateInterface(); } void CMultiMode::hostTCP() @@ -547,7 +543,7 @@ void CMultiPlayers::enterSelectionScreen() Settings name = settings.write["general"]["playerName"]; name->String() = names[0]; - CMainMenu::openLobby(screenType, host, &names, loadMode); + CMainMenu::openLobby(screenType, host, names, loadMode); } CSimpleJoinScreen::CSimpleJoinScreen(bool host) diff --git a/client/mainmenu/CMainMenu.h b/client/mainmenu/CMainMenu.h index 5c806961f..7ef4fef8d 100644 --- a/client/mainmenu/CMainMenu.h +++ b/client/mainmenu/CMainMenu.h @@ -31,11 +31,11 @@ class CLabel; // TODO: Find new location for these enums -enum ESelectionScreen : ui8 { +enum class ESelectionScreen : ui8 { unknown = 0, newGame, loadGame, saveGame, scenarioInfo, campaignList }; -enum ELoadMode : ui8 +enum class ELoadMode : ui8 { NONE = 0, SINGLE, MULTI, CAMPAIGN, TUTORIAL }; @@ -150,7 +150,7 @@ public: void activate() override; void onScreenResize() override; void update() override; - static void openLobby(ESelectionScreen screenType, bool host, const std::vector * names, ELoadMode loadMode); + static void openLobby(ESelectionScreen screenType, bool host, const std::vector & names, ELoadMode loadMode); static void openCampaignLobby(const std::string & campaignFileName, std::string campaignSet = ""); static void openCampaignLobby(std::shared_ptr campaign); static void startTutorial(); diff --git a/client/windows/CMapOverview.h b/client/windows/CMapOverview.h index 1602e80c3..bbd0b0e79 100644 --- a/client/windows/CMapOverview.h +++ b/client/windows/CMapOverview.h @@ -24,7 +24,7 @@ class CTextBox; class IImage; class Canvas; class TransparentFilledRectangle; -enum ESelectionScreen : ui8; +enum class ESelectionScreen : ui8; class CMapOverview; diff --git a/config/widgets/lobbyWindow.json b/config/widgets/lobbyWindow.json index dfbf90d00..e8bdc8889 100644 --- a/config/widgets/lobbyWindow.json +++ b/config/widgets/lobbyWindow.json @@ -45,25 +45,23 @@ "type": "labelTitleMain", "position": {"x": 15, "y": 10} }, - + { "type": "areaFilled", - "rect": {"x": 5, "y": 50, "w": 250, "h": 150} + "rect": {"x": 5, "y": 50, "w": 250, "h": 500} }, { "type": "labelTitle", "position": {"x": 15, "y": 53}, - "text" : "Match Filter" - }, - - { - "type": "areaFilled", - "rect": {"x": 5, "y": 210, "w": 250, "h": 340} + "text" : "Room List" }, { - "type": "labelTitle", - "position": {"x": 15, "y": 213}, - "text" : "Match List" + "type" : "roomList", + "name" : "roomList", + "position" : { "x" : 7, "y" : 69 }, + "itemOffset" : { "x" : 0, "y" : 40 }, + "sliderPosition" : { "x" : 230, "y" : 0 }, + "sliderSize" : { "x" : 450, "y" : 450 } }, { @@ -112,9 +110,17 @@ { "type": "labelTitle", "position": {"x": 880, "y": 53}, - "text" : "Player List" + "text" : "Account List" }, - + { + "type" : "accountList", + "name" : "accountList", + "position" : { "x" : 872, "y" : 69 }, + "itemOffset" : { "x" : 0, "y" : 40 }, + "sliderPosition" : { "x" : 130, "y" : 0 }, + "sliderSize" : { "x" : 520, "y" : 520 } + }, + { "type": "button", "position": {"x": 840, "y": 10}, diff --git a/lib/StartInfo.cpp b/lib/StartInfo.cpp index 8b971f4e7..73b896716 100644 --- a/lib/StartInfo.cpp +++ b/lib/StartInfo.cpp @@ -111,7 +111,7 @@ void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const if(i == si->playerInfos.cend() && !ignoreNoHuman) throw std::domain_error(VLC->generaltexth->translate("core.genrltxt.530")); - if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME) + if(si->mapGenOptions && si->mode == EStartMode::NEW_GAME) { if(!si->mapGenOptions->checkOptions()) throw std::domain_error(VLC->generaltexth->translate("core.genrltxt.751")); diff --git a/lib/StartInfo.h b/lib/StartInfo.h index 8e0684ba2..2a2ad3703 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -98,12 +98,18 @@ struct DLL_LINKAGE PlayerSettings HeroTypeID getHeroValidated() const; }; +enum class EStartMode : int32_t +{ + NEW_GAME, + LOAD_GAME, + CAMPAIGN, + INVALID = 255 +}; + /// Struct which describes the difficulty, the turn time,.. of a heroes match. struct DLL_LINKAGE StartInfo { - enum EMode {NEW_GAME, LOAD_GAME, CAMPAIGN, INVALID = 255}; - - EMode mode; + EStartMode mode; ui8 difficulty; //0=easy; 4=impossible using TPlayerInfos = std::map; @@ -152,7 +158,7 @@ struct DLL_LINKAGE StartInfo h & campState; } - StartInfo() : mode(INVALID), difficulty(1), seedToBeUsed(0), seedPostInit(0), + StartInfo() : mode(EStartMode::INVALID), difficulty(1), seedToBeUsed(0), seedPostInit(0), mapfileChecksum(0), startTimeIso8601(vstd::getDateTimeISO8601Basic(std::time(nullptr))), fileURI("") { diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 6987dd802..56184d2af 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -189,10 +189,10 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, Load::Prog switch(scenarioOps->mode) { - case StartInfo::NEW_GAME: + case EStartMode::NEW_GAME: initNewGame(mapService, allowSavingRandomMap, progressTracking); break; - case StartInfo::CAMPAIGN: + case EStartMode::CAMPAIGN: initCampaign(); break; default: @@ -711,7 +711,7 @@ void CGameState::initFogOfWar() void CGameState::initStartingBonus() { - if (scenarioOps->mode == StartInfo::CAMPAIGN) + if (scenarioOps->mode == EStartMode::CAMPAIGN) return; // These are the single scenario bonuses; predefined // campaign bonuses are spread out over other init* functions. diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index 8887c8212..a29a27c84 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -39,7 +39,7 @@ CampaignHeroReplacement::CampaignHeroReplacement(CGHeroInstance * hero, const Ob CGameStateCampaign::CGameStateCampaign(CGameState * owner): gameState(owner) { - assert(gameState->scenarioOps->mode == StartInfo::CAMPAIGN); + assert(gameState->scenarioOps->mode == EStartMode::CAMPAIGN); assert(gameState->scenarioOps->campState != nullptr); } diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index f4ae146e2..3ab8279d8 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -95,7 +95,10 @@ void NetworkConnection::sendPacket(const std::vector & message) ostream.write(reinterpret_cast(&messageSize), messageHeaderSize); ostream.write(reinterpret_cast(message.data()), message.size()); - boost::asio::write(*socket, writeBuffer ); + boost::system::error_code ec; + boost::asio::write(*socket, writeBuffer, ec ); + + // FIXME: handle error? } VCMI_LIB_NAMESPACE_END diff --git a/lib/networkPacks/PacksForLobby.h b/lib/networkPacks/PacksForLobby.h index d5ef0b7b5..fb6f06338 100644 --- a/lib/networkPacks/PacksForLobby.h +++ b/lib/networkPacks/PacksForLobby.h @@ -37,7 +37,7 @@ struct DLL_LINKAGE LobbyClientConnected : public CLobbyPackToPropagate // Set by client before sending pack to server std::string uuid; std::vector names; - StartInfo::EMode mode = StartInfo::INVALID; + EStartMode mode = EStartMode::INVALID; // Changed by server before announcing pack int clientId = -1; int hostClientId = -1; diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index fe6e5b681..0105d6a34 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -106,6 +106,11 @@ bool CConnection::isMyConnection(const std::shared_ptr & oth return otherConnection != nullptr && networkConnection.lock() == otherConnection; } +std::shared_ptr CConnection::getConnection() +{ + return networkConnection.lock(); +} + void CConnection::disableStackSendingByID() { packReader->sendStackInstanceByIds = false; diff --git a/lib/serializer/Connection.h b/lib/serializer/Connection.h index 785f85f99..4de171a82 100644 --- a/lib/serializer/Connection.h +++ b/lib/serializer/Connection.h @@ -41,6 +41,7 @@ class DLL_LINKAGE CConnection : boost::noncopyable public: bool isMyConnection(const std::shared_ptr & otherConnection) const; + std::shared_ptr getConnection(); std::string uuid; int connectionID; diff --git a/lobby/LobbyDatabase.cpp b/lobby/LobbyDatabase.cpp index 30332b4e4..c4a40aa13 100644 --- a/lobby/LobbyDatabase.cpp +++ b/lobby/LobbyDatabase.cpp @@ -96,7 +96,7 @@ void LobbyDatabase::prepareStatements() )"; static const std::string insertGameRoomText = R"( - INSERT INTO gameRooms(roomID, hostAccountID, status, playerLimit) VALUES(?, ?, 'empty', 8); + INSERT INTO gameRooms(roomID, hostAccountID, status, playerLimit) VALUES(?, ?, 0, 8); )"; static const std::string insertGameRoomPlayersText = R"( @@ -119,6 +119,12 @@ void LobbyDatabase::prepareStatements() // UPDATE + static const std::string setAccountOnlineText = R"( + UPDATE accounts + SET online = ? + WHERE accountID = ? + )"; + static const std::string setGameRoomStatusText = R"( UPDATE gameRooms SET status = ? @@ -145,7 +151,7 @@ void LobbyDatabase::prepareStatements() static const std::string getIdleGameRoomText = R"( SELECT roomID FROM gameRooms - WHERE hostAccountID = ? AND status = 'idle' + WHERE hostAccountID = ? AND status = 0 LIMIT 1 )"; @@ -153,7 +159,7 @@ void LobbyDatabase::prepareStatements() SELECT grp.roomID FROM gameRoomPlayers grp LEFT JOIN gameRooms gr ON gr.roomID = grp.roomID - WHERE accountID = ? AND status IN ('public', 'private', 'busy') + WHERE accountID = ? AND status IN (1, 2) LIMIT 1 )"; @@ -163,6 +169,13 @@ void LobbyDatabase::prepareStatements() WHERE online = 1 )"; + static const std::string getActiveGameRoomsText = R"( + SELECT roomID, hostAccountID, displayName, status, 0, playerLimit + FROM gameRooms + LEFT JOIN accounts ON hostAccountID = accountID + WHERE status = 1 + )"; + static const std::string getAccountDisplayNameText = R"( SELECT displayName FROM accounts @@ -216,6 +229,7 @@ void LobbyDatabase::prepareStatements() deleteGameRoomPlayersStatement = database->prepare(deleteGameRoomPlayersText); deleteGameRoomInvitesStatement = database->prepare(deleteGameRoomInvitesText); + setAccountOnlineStatement = database->prepare(setAccountOnlineText); setGameRoomStatusStatement = database->prepare(setGameRoomStatusText); setGameRoomPlayerLimitStatement = database->prepare(setGameRoomPlayerLimitText); @@ -223,6 +237,7 @@ void LobbyDatabase::prepareStatements() getIdleGameRoomStatement = database->prepare(getIdleGameRoomText); getAccountGameRoomStatement = database->prepare(getAccountGameRoomText); getActiveAccountsStatement = database->prepare(getActiveAccountsText); + getActiveGameRoomsStatement = database->prepare(getActiveGameRoomsText); getAccountDisplayNameStatement = database->prepare(getAccountDisplayNameText); isAccountCookieValidStatement = database->prepare(isAccountCookieValidText); @@ -285,6 +300,11 @@ std::vector LobbyDatabase::getRecentMessageHistory() return result; } +void LobbyDatabase::setAccountOnline(const std::string & accountID, bool isOnline) +{ + setAccountOnlineStatement->executeOnce(isOnline ? 1 : 0, accountID); +} + void LobbyDatabase::setGameRoomStatus(const std::string & roomID, LobbyRoomState roomStatus) { setGameRoomStatusStatement->executeOnce(vstd::to_underlying(roomStatus), roomID); @@ -404,7 +424,16 @@ bool LobbyDatabase::isAccountIDExists(const std::string & accountID) std::vector LobbyDatabase::getActiveGameRooms() { - return {}; + std::vector result; + + while(getActiveGameRoomsStatement->execute()) + { + LobbyGameRoom entry; + getActiveGameRoomsStatement->getColumns(entry.roomID, entry.hostAccountID, entry.hostAccountDisplayName, entry.roomStatus, entry.playersCount, entry.playersLimit); + result.push_back(entry); + } + getActiveGameRoomsStatement->reset(); + return result; } std::vector LobbyDatabase::getActiveAccounts() @@ -425,6 +454,7 @@ std::string LobbyDatabase::getIdleGameRoom(const std::string & hostAccountID) { std::string result; + getIdleGameRoomStatement->setBinds(hostAccountID); if(getIdleGameRoomStatement->execute()) getIdleGameRoomStatement->getColumns(result); @@ -436,6 +466,7 @@ std::string LobbyDatabase::getAccountGameRoom(const std::string & accountID) { std::string result; + getAccountGameRoomStatement->setBinds(accountID); if(getAccountGameRoomStatement->execute()) getAccountGameRoomStatement->getColumns(result); diff --git a/lobby/LobbyDatabase.h b/lobby/LobbyDatabase.h index 9768ff886..898256020 100644 --- a/lobby/LobbyDatabase.h +++ b/lobby/LobbyDatabase.h @@ -31,13 +31,15 @@ class LobbyDatabase SQLiteStatementPtr deleteGameRoomPlayersStatement; SQLiteStatementPtr deleteGameRoomInvitesStatement; + SQLiteStatementPtr setAccountOnlineStatement; SQLiteStatementPtr setGameRoomStatusStatement; SQLiteStatementPtr setGameRoomPlayerLimitStatement; SQLiteStatementPtr getRecentMessageHistoryStatement; SQLiteStatementPtr getIdleGameRoomStatement; - SQLiteStatementPtr getAccountGameRoomStatement; + SQLiteStatementPtr getActiveGameRoomsStatement; SQLiteStatementPtr getActiveAccountsStatement; + SQLiteStatementPtr getAccountGameRoomStatement; SQLiteStatementPtr getAccountDisplayNameStatement; SQLiteStatementPtr isAccountCookieValidStatement; @@ -54,6 +56,7 @@ public: explicit LobbyDatabase(const boost::filesystem::path & databasePath); ~LobbyDatabase(); + void setAccountOnline(const std::string & accountID, bool isOnline); void setGameRoomStatus(const std::string & roomID, LobbyRoomState roomStatus); void setGameRoomPlayerLimit(const std::string & roomID, uint32_t playerLimit); diff --git a/lobby/LobbyDefines.h b/lobby/LobbyDefines.h index 45a851404..789c79028 100644 --- a/lobby/LobbyDefines.h +++ b/lobby/LobbyDefines.h @@ -18,7 +18,9 @@ struct LobbyAccount struct LobbyGameRoom { - std::string roomUUID; + std::string roomID; + std::string hostAccountID; + std::string hostAccountDisplayName; std::string roomStatus; uint32_t playersCount; uint32_t playersLimit; @@ -48,10 +50,10 @@ enum class LobbyInviteStatus : int32_t enum class LobbyRoomState : int32_t { - IDLE, // server is ready but no players are in the room - PUBLIC, // host has joined and allows anybody to join - PRIVATE, // host has joined but only allows those he invited to join - //BUSY, // match is ongoing - //CANCELLED, // game room was cancelled without starting the game - //CLOSED, // game room was closed after playing for some time + IDLE = 0, // server is ready but no players are in the room + PUBLIC = 1, // host has joined and allows anybody to join + PRIVATE = 2, // host has joined but only allows those he invited to join + //BUSY = 3, // match is ongoing + //CANCELLED = 4, // game room was cancelled without starting the game + CLOSED = 5, // game room was closed after playing for some time }; diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 6f079e41f..691dd417c 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -140,7 +140,7 @@ void LobbyServer::broadcastActiveAccounts() JsonNode jsonEntry; jsonEntry["accountID"].String() = account.accountID; jsonEntry["displayName"].String() = account.displayName; - // jsonEntry["status"].String() = account.status; + jsonEntry["status"].String() = "In Lobby"; // TODO: in room status, in match status, offline status(?) reply["accounts"].Vector().push_back(jsonEntry); } @@ -157,10 +157,13 @@ void LobbyServer::broadcastActiveGameRooms() for(const auto & gameRoom : activeGameRoomStats) { JsonNode jsonEntry; - jsonEntry["gameRoomID"].String() = gameRoom.roomUUID; - jsonEntry["status"].String() = gameRoom.roomStatus; - jsonEntry["status"].Integer() = gameRoom.playersCount; - jsonEntry["status"].Integer() = gameRoom.playersLimit; + jsonEntry["gameRoomID"].String() = gameRoom.roomID; + jsonEntry["hostAccountID"].String() = gameRoom.hostAccountID; + jsonEntry["hostAccountDisplayName"].String() = gameRoom.hostAccountDisplayName; + jsonEntry["description"].String() = "TODO: ROOM DESCRIPTION"; +// jsonEntry["status"].String() = gameRoom.roomStatus; + jsonEntry["playersCount"].Integer() = gameRoom.playersCount; + jsonEntry["playersLimit"].Integer() = gameRoom.playersLimit; reply["gameRooms"].Vector().push_back(jsonEntry); } @@ -204,6 +207,12 @@ void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection) void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection) { + if (activeAccounts.count(connection)) + database->setAccountOnline(activeAccounts.at(connection).accountID, false); + + if (activeGameRooms.count(connection)) + database->setGameRoomStatus(activeGameRooms.at(connection).roomID, LobbyRoomState::CLOSED); + // NOTE: lost connection can be in only one of these lists (or in none of them) // calling on all possible containers since calling std::map::erase() with non-existing key is legal activeAccounts.erase(connection); @@ -334,6 +343,7 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co // prolong existing cookie database->updateAccessCookie(accountID, accountCookie); database->updateAccountLoginTime(accountID); + database->setAccountOnline(accountID, true); std::string displayName = database->getAccountDisplayName(accountID); @@ -369,6 +379,8 @@ void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, co activeGameRooms[connection].roomID = gameRoomID; sendLoginSuccess(connection, accountCookie, {}); } + + broadcastActiveGameRooms(); } void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json) @@ -451,7 +463,7 @@ void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, c database->setGameRoomStatus(gameRoomID, LobbyRoomState::PRIVATE); // TODO: additional flags / initial settings, e.g. allowCheats - // TODO: connection mode: direct or proxy. For now direct is assumed + // TODO: connection mode: direct or proxy. For now direct is assumed. Proxy might be needed later, for hosted servers broadcastActiveGameRooms(); sendJoinRoomSuccess(connection, gameRoomID); diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 5eb40861f..efc6a0432 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -15,6 +15,7 @@ set(server_SRCS processors/PlayerMessageProcessor.cpp processors/TurnOrderProcessor.cpp + EntryPoint.cpp CGameHandler.cpp GlobalLobbyProcessor.cpp ServerSpellCastEnvironment.cpp diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index f6643a803..ca7d5003f 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -10,44 +10,15 @@ #include "StdInc.h" #include "CVCMIServer.h" -#include "../lib/filesystem/Filesystem.h" -#include "../lib/campaign/CampaignState.h" -#include "../lib/CThreadHelper.h" -#include "../lib/CArtHandler.h" -#include "../lib/CGeneralTextHandler.h" -#include "../lib/CHeroHandler.h" -#include "../lib/CTownHandler.h" -#include "../lib/CBuildingHandler.h" -#include "../lib/spells/CSpellHandler.h" -#include "../lib/CCreatureHandler.h" -#include "zlib.h" -#include "../lib/StartInfo.h" -#include "../lib/mapping/CMapHeader.h" -#include "../lib/rmg/CMapGenOptions.h" -#include "LobbyNetPackVisitors.h" -#ifdef VCMI_ANDROID -#include -#include -#include "lib/CAndroidVMHelper.h" -#endif -#include "../lib/VCMI_Lib.h" -#include "../lib/VCMIDirs.h" #include "CGameHandler.h" #include "GlobalLobbyProcessor.h" +#include "LobbyNetPackVisitors.h" #include "processors/PlayerMessageProcessor.h" -#include "../lib/mapping/CMapInfo.h" -#include "../lib/GameConstants.h" -#include "../lib/logging/CBasicLogConfigurator.h" -#include "../lib/CConfigHandler.h" -#include "../lib/ScopeGuard.h" -#include "../lib/serializer/CMemorySerializer.h" -#include "../lib/serializer/Cast.h" -#include "../lib/serializer/Connection.h" -#include "../lib/UnlockGuard.h" - -// for applier +#include "../lib/CHeroHandler.h" #include "../lib/registerTypes/RegisterTypesLobbyPacks.h" +#include "../lib/serializer/CMemorySerializer.h" +#include "../lib/serializer/Connection.h" // UUID generation #include @@ -55,8 +26,6 @@ #include #include -#include "../lib/gameState/CGameState.h" - template class CApplyOnServer; class CBaseForServerApply @@ -147,9 +116,6 @@ public: } }; -std::string SERVER_NAME_AFFIX = "server"; -std::string SERVER_NAME = GameConstants::VCMI_VERSION + std::string(" (") + SERVER_NAME_AFFIX + ')'; - CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts) : state(EServerState::LOBBY), cmdLineOptions(opts), currentClientId(1), currentPlayerId(1), restartGameplay(false) { @@ -158,20 +124,29 @@ CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts) applier = std::make_shared>(); registerTypesLobbyPacks(*applier); + networkHandler = INetworkHandler::createHandler(); + + if(cmdLineOptions.count("lobby")) + lobbyProcessor = std::make_unique(*this); + else + startAcceptingIncomingConnections(); +} + +CVCMIServer::~CVCMIServer() = default; + +void CVCMIServer::startAcceptingIncomingConnections() +{ uint16_t port = 3030; + if(cmdLineOptions.count("port")) port = cmdLineOptions["port"].as(); logNetwork->info("Port %d will be used", port); - networkHandler = INetworkHandler::createHandler(); networkServer = networkHandler->createServerTCP(*this); networkServer->start(port); - establishOutgoingConnection(); logNetwork->info("Listening for connections at port %d", port); } -CVCMIServer::~CVCMIServer() = default; - void CVCMIServer::onNewConnection(const std::shared_ptr & connection) { if(state == EServerState::LOBBY) @@ -206,7 +181,7 @@ EServerState CVCMIServer::getState() const std::shared_ptr CVCMIServer::findConnection(const std::shared_ptr & netConnection) { - for (auto const & gameConnection : activeConnections) + for(const auto & gameConnection : activeConnections) { if (gameConnection->isMyConnection(netConnection)) return gameConnection; @@ -249,12 +224,6 @@ void CVCMIServer::onTimer() networkHandler->createTimer(*this, serverUpdateInterval); } -void CVCMIServer::establishOutgoingConnection() -{ - if(cmdLineOptions.count("lobby")) - lobbyProcessor = std::make_unique(*this); -} - void CVCMIServer::prepareToRestart() { if(state == EServerState::GAMEPLAY) @@ -304,7 +273,7 @@ bool CVCMIServer::prepareToStartGame() gh = std::make_shared(this); switch(si->mode) { - case StartInfo::CAMPAIGN: + case EStartMode::CAMPAIGN: logNetwork->info("Preparing to start new campaign"); si->startTimeIso8601 = vstd::getDateTimeISO8601Basic(std::time(nullptr)); si->fileURI = mi->fileURI; @@ -313,14 +282,14 @@ bool CVCMIServer::prepareToStartGame() gh->init(si.get(), progressTracking); break; - case StartInfo::NEW_GAME: + case EStartMode::NEW_GAME: logNetwork->info("Preparing to start new game"); si->startTimeIso8601 = vstd::getDateTimeISO8601Basic(std::time(nullptr)); si->fileURI = mi->fileURI; gh->init(si.get(), progressTracking); break; - case StartInfo::LOAD_GAME: + case EStartMode::LOAD_GAME: logNetwork->info("Preparing to start loaded game"); if(!gh->load(si->mapname)) { @@ -346,7 +315,7 @@ void CVCMIServer::startGameImmediately() for(auto c : activeConnections) c->enterGameplayConnectionMode(gh->gs); - gh->start(si->mode == StartInfo::LOAD_GAME); + gh->start(si->mode == EStartMode::LOAD_GAME); state = EServerState::GAMEPLAY; lastTimerUpdateTime = gameplayStartTime = std::chrono::steady_clock::now(); onTimer(); @@ -360,7 +329,11 @@ void CVCMIServer::onDisconnected(const std::shared_ptr & con vstd::erase(activeConnections, c); if(activeConnections.empty() || hostClientId == c->connectionID) + { + networkHandler->stop(); state = EServerState::SHUTDOWN; + return; + } if(gh && state == EServerState::GAMEPLAY) { @@ -428,7 +401,7 @@ bool CVCMIServer::passHost(int toConnectionId) return false; } -void CVCMIServer::clientConnected(std::shared_ptr c, std::vector & names, std::string uuid, StartInfo::EMode mode) +void CVCMIServer::clientConnected(std::shared_ptr c, std::vector & names, std::string uuid, EStartMode mode) { if(state != EServerState::LOBBY) throw std::runtime_error("CVCMIServer::clientConnected called while game is not accepting clients!"); @@ -468,16 +441,16 @@ void CVCMIServer::clientConnected(std::shared_ptr c, std::vector c) { + networkServer->closeConnection(c->getConnection()); vstd::erase(activeConnections, c); if(activeConnections.empty() || hostClientId == c->connectionID) { + networkHandler->stop(); state = EServerState::SHUTDOWN; return; } - // TODO: close network connection - // PlayerReinitInterface startAiPack; // startAiPack.playerConnectionId = PlayerSettings::PLAYER_AI; // @@ -565,7 +538,7 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr mapInfo, if(mi->scenarioOptionsOfSave) { si = CMemorySerializer::deepCopy(*mi->scenarioOptionsOfSave); - si->mode = StartInfo::LOAD_GAME; + si->mode = EStartMode::LOAD_GAME; if(si->campState) campaignMap = si->campState->currentScenario().value(); @@ -581,7 +554,7 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr mapInfo, } } } - else if(si->mode == StartInfo::NEW_GAME || si->mode == StartInfo::CAMPAIGN) + else if(si->mode == EStartMode::NEW_GAME || si->mode == EStartMode::CAMPAIGN) { if(mi->campaign) return; @@ -634,7 +607,7 @@ void CVCMIServer::updateAndPropagateLobbyState() { // Update player settings for RMG // TODO: find appropriate location for this code - if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME) + if(si->mapGenOptions && si->mode == EStartMode::NEW_GAME) { for(const auto & psetPair : si->playerInfos) { @@ -971,140 +944,3 @@ ui8 CVCMIServer::getIdOfFirstUnallocatedPlayer() const return 0; } - -static void handleCommandOptions(int argc, const char * argv[], boost::program_options::variables_map & options) -{ - namespace po = boost::program_options; - po::options_description opts("Allowed options"); - opts.add_options() - ("help,h", "display help and exit") - ("version,v", "display version information and exit") - ("run-by-client", "indicate that server launched by client on same machine") - ("port", po::value(), "port at which server will listen to connections from client") - ("lobby", "start server in lobby mode in which server connects to a global lobby"); - - if(argc > 1) - { - try - { - po::store(po::parse_command_line(argc, argv, opts), options); - } - catch(boost::program_options::error & e) - { - std::cerr << "Failure during parsing command-line options:\n" << e.what() << std::endl; - } - } - -#ifdef SINGLE_PROCESS_APP - options.emplace("run-by-client", po::variable_value{true, true}); -#endif - - po::notify(options); - -#ifndef SINGLE_PROCESS_APP - if(options.count("help")) - { - auto time = std::time(nullptr); - printf("%s - A Heroes of Might and Magic 3 clone\n", GameConstants::VCMI_VERSION.c_str()); - printf("Copyright (C) 2007-%d VCMI dev team - see AUTHORS file\n", std::localtime(&time)->tm_year + 1900); - printf("This is free software; see the source for copying conditions. There is NO\n"); - printf("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"); - printf("\n"); - std::cout << opts; - exit(0); - } - - if(options.count("version")) - { - printf("%s\n", GameConstants::VCMI_VERSION.c_str()); - std::cout << VCMIDirs::get().genHelpString(); - exit(0); - } -#endif -} - -#ifdef SINGLE_PROCESS_APP -#define main server_main -#endif - -#if VCMI_ANDROID_DUAL_PROCESS -void CVCMIServer::create() -{ - const int argc = 1; - const char * argv[argc] = { "android-server" }; -#else -int main(int argc, const char * argv[]) -{ -#endif - -#if !defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP) - // Correct working dir executable folder (not bundle folder) so we can use executable relative paths - boost::filesystem::current_path(boost::filesystem::system_complete(argv[0]).parent_path()); -#endif - -#ifndef VCMI_IOS - console = new CConsoleHandler(); -#endif - CBasicLogConfigurator logConfig(VCMIDirs::get().userLogsPath() / "VCMI_Server_log.txt", console); - logConfig.configureDefault(); - logGlobal->info(SERVER_NAME); - - boost::program_options::variables_map opts; - handleCommandOptions(argc, argv, opts); - preinitDLL(console, false); - logConfig.configure(); - - loadDLLClasses(); - srand((ui32)time(nullptr)); - - CVCMIServer server(opts); - -#ifdef SINGLE_PROCESS_APP - boost::condition_variable * cond = reinterpret_cast(const_cast(argv[0])); - cond->notify_one(); -#endif - - server.run(); - -#if VCMI_ANDROID_DUAL_PROCESS - CAndroidVMHelper envHelper; - envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer"); -#endif - logConfig.deconfigure(); - vstd::clear_pointer(VLC); - -#if !VCMI_ANDROID_DUAL_PROCESS - return 0; -#endif -} - -#if VCMI_ANDROID_DUAL_PROCESS -extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_createServer(JNIEnv * env, jclass cls) -{ - __android_log_write(ANDROID_LOG_INFO, "VCMI", "Got jni call to init server"); - CAndroidVMHelper::cacheVM(env); - - CVCMIServer::create(); -} - -extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_initClassloader(JNIEnv * baseEnv, jclass cls) -{ - CAndroidVMHelper::initClassloader(baseEnv); -} -#elif defined(SINGLE_PROCESS_APP) -void CVCMIServer::create(boost::condition_variable * cond, const std::vector & args) -{ - std::vector argv = {cond}; - for(auto & a : args) - argv.push_back(a.c_str()); - main(argv.size(), reinterpret_cast(&*argv.begin())); -} - -#ifdef VCMI_ANDROID -void CVCMIServer::reuseClientJNIEnv(void * jniEnv) -{ - CAndroidVMHelper::initClassloader(jniEnv); - CAndroidVMHelper::alwaysUseLoadedClass = true; -} -#endif // VCMI_ANDROID -#endif // VCMI_ANDROID_DUAL_PROCESS diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 7b366339b..a7da4028e 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -70,8 +70,6 @@ private: std::shared_ptr> applier; EServerState state; - void establishOutgoingConnection(); - std::shared_ptr findConnection(const std::shared_ptr &); int currentClientId; @@ -84,7 +82,6 @@ public: void onNewConnection(const std::shared_ptr &) override; void onTimer() override; - std::shared_ptr gh; boost::program_options::variables_map cmdLineOptions; @@ -96,6 +93,7 @@ public: bool prepareToStartGame(); void prepareToRestart(); void startGameImmediately(); + void startAcceptingIncomingConnections(); void threadHandleClient(std::shared_ptr c); @@ -107,7 +105,7 @@ public: void setPlayerConnectedId(PlayerSettings & pset, ui8 player) const; void updateStartInfoOnMapChange(std::shared_ptr mapInfo, std::shared_ptr mapGenOpt = {}); - void clientConnected(std::shared_ptr c, std::vector & names, std::string uuid, StartInfo::EMode mode); + void clientConnected(std::shared_ptr c, std::vector & names, std::string uuid, EStartMode mode); void clientDisconnected(std::shared_ptr c); void reconnectPlayer(int connId); diff --git a/server/EntryPoint.cpp b/server/EntryPoint.cpp new file mode 100644 index 000000000..d0f154c22 --- /dev/null +++ b/server/EntryPoint.cpp @@ -0,0 +1,170 @@ +/* + * EntryPoint.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include "CVCMIServer.h" + +#include "../lib/CConsoleHandler.h" +#include "../lib/logging/CBasicLogConfigurator.h" +#include "../lib/VCMIDirs.h" +#include "../lib/VCMI_Lib.h" + +#ifdef VCMI_ANDROID +#include +#include +#include "lib/CAndroidVMHelper.h" +#endif + +#include + +std::string SERVER_NAME_AFFIX = "server"; +std::string SERVER_NAME = GameConstants::VCMI_VERSION + std::string(" (") + SERVER_NAME_AFFIX + ')'; + +static void handleCommandOptions(int argc, const char * argv[], boost::program_options::variables_map & options) +{ + namespace po = boost::program_options; + po::options_description opts("Allowed options"); + opts.add_options() + ("help,h", "display help and exit") + ("version,v", "display version information and exit") + ("run-by-client", "indicate that server launched by client on same machine") + ("port", po::value(), "port at which server will listen to connections from client") + ("lobby", "start server in lobby mode in which server connects to a global lobby"); + + if(argc > 1) + { + try + { + po::store(po::parse_command_line(argc, argv, opts), options); + } + catch(boost::program_options::error & e) + { + std::cerr << "Failure during parsing command-line options:\n" << e.what() << std::endl; + } + } + +#ifdef SINGLE_PROCESS_APP + options.emplace("run-by-client", po::variable_value{true, true}); +#endif + + po::notify(options); + +#ifndef SINGLE_PROCESS_APP + if(options.count("help")) + { + auto time = std::time(nullptr); + printf("%s - A Heroes of Might and Magic 3 clone\n", GameConstants::VCMI_VERSION.c_str()); + printf("Copyright (C) 2007-%d VCMI dev team - see AUTHORS file\n", std::localtime(&time)->tm_year + 1900); + printf("This is free software; see the source for copying conditions. There is NO\n"); + printf("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"); + printf("\n"); + std::cout << opts; + exit(0); + } + + if(options.count("version")) + { + printf("%s\n", GameConstants::VCMI_VERSION.c_str()); + std::cout << VCMIDirs::get().genHelpString(); + exit(0); + } +#endif +} + +#ifdef SINGLE_PROCESS_APP +#define main server_main +#endif + +#if VCMI_ANDROID_DUAL_PROCESS +void CVCMIServer::create() +{ + const int argc = 1; + const char * argv[argc] = { "android-server" }; +#else +int main(int argc, const char * argv[]) +{ +#endif + +#if !defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP) + // Correct working dir executable folder (not bundle folder) so we can use executable relative paths + boost::filesystem::current_path(boost::filesystem::system_complete(argv[0]).parent_path()); +#endif + +#ifndef VCMI_IOS + console = new CConsoleHandler(); +#endif + CBasicLogConfigurator logConfig(VCMIDirs::get().userLogsPath() / "VCMI_Server_log.txt", console); + logConfig.configureDefault(); + logGlobal->info(SERVER_NAME); + + boost::program_options::variables_map opts; + handleCommandOptions(argc, argv, opts); + preinitDLL(console, false); + logConfig.configure(); + + loadDLLClasses(); + srand((ui32)time(nullptr)); + + { + CVCMIServer server(opts); + +#ifdef SINGLE_PROCESS_APP + boost::condition_variable * cond = reinterpret_cast(const_cast(argv[0])); + cond->notify_one(); +#endif + + server.run(); + + // CVCMIServer destructor must be called here - before VLC cleanup + } + + +#if VCMI_ANDROID_DUAL_PROCESS + CAndroidVMHelper envHelper; + envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer"); +#endif + logConfig.deconfigure(); + vstd::clear_pointer(VLC); + +#if !VCMI_ANDROID_DUAL_PROCESS + return 0; +#endif +} + +#if VCMI_ANDROID_DUAL_PROCESS +extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_createServer(JNIEnv * env, jclass cls) +{ + __android_log_write(ANDROID_LOG_INFO, "VCMI", "Got jni call to init server"); + CAndroidVMHelper::cacheVM(env); + + CVCMIServer::create(); +} + +extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_initClassloader(JNIEnv * baseEnv, jclass cls) +{ + CAndroidVMHelper::initClassloader(baseEnv); +} +#elif defined(SINGLE_PROCESS_APP) +void CVCMIServer::create(boost::condition_variable * cond, const std::vector & args) +{ + std::vector argv = {cond}; + for(auto & a : args) + argv.push_back(a.c_str()); + main(argv.size(), reinterpret_cast(&*argv.begin())); +} + +#ifdef VCMI_ANDROID +void CVCMIServer::reuseClientJNIEnv(void * jniEnv) +{ + CAndroidVMHelper::initClassloader(jniEnv); + CAndroidVMHelper::alwaysUseLoadedClass = true; +} +#endif // VCMI_ANDROID +#endif // VCMI_ANDROID_DUAL_PROCESS diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index 2ed1bd2e8..a4ad75b21 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -57,6 +57,7 @@ void GlobalLobbyProcessor::receiveLoginSuccess(const JsonNode & json) { // no-op, wait just for any new commands from lobby logGlobal->info("Succesfully connected to lobby server"); + owner.startAcceptingIncomingConnections(); } void GlobalLobbyProcessor::receiveAccountJoinsRoom(const JsonNode & json) @@ -83,6 +84,7 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptrmapname = pack.ourCampaign->getFilename(); - srv.si->mode = StartInfo::CAMPAIGN; + srv.si->mode = EStartMode::CAMPAIGN; srv.si->campState = pack.ourCampaign; srv.si->turnTimerInfo = TurnTimerInfo{}; From eaca128c99b463cb32012b3563ef47ba93c7d5b2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 26 Jan 2024 17:40:31 +0200 Subject: [PATCH 40/98] Code cleanup --- client/CServerHandler.cpp | 12 ++++++------ client/NetPacksLobbyClient.cpp | 2 +- lib/network/NetworkServer.cpp | 2 +- lobby/LobbyServer.cpp | 14 +++++++------- lobby/LobbyServer.h | 8 ++++---- lobby/SQLiteConnection.cpp | 6 ++---- server/CVCMIServer.cpp | 8 ++++++-- server/CVCMIServer.h | 2 +- server/EntryPoint.cpp | 6 +++--- test/game/CGameStateTest.cpp | 2 +- 10 files changed, 32 insertions(+), 30 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 1b77f01f4..bf0a7b27b 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -133,14 +133,14 @@ CServerHandler::~CServerHandler() } CServerHandler::CServerHandler() - : state(EClientState::NONE) - , networkHandler(INetworkHandler::createHandler()) - , applier(std::make_unique>()) + : applier(std::make_unique>()) , lobbyClient(std::make_unique()) - , client(nullptr) - , loadMode(ELoadMode::NONE) - , screenType(ESelectionScreen::unknown) + , networkHandler(INetworkHandler::createHandler()) + , state(EClientState::NONE) , campaignStateToSend(nullptr) + , screenType(ESelectionScreen::unknown) + , loadMode(ELoadMode::NONE) + , client(nullptr) , campaignServerRestartLock(false) { uuid = boost::uuids::to_string(boost::uuids::random_generator()()); diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index b7045dea7..83dd2ce5b 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -69,7 +69,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon GH.windows().popWindows(1); } - GH.windows().createAndPushWindow(static_cast(handler.screenType)); + GH.windows().createAndPushWindow(handler.screenType); } handler.state = EClientState::LOBBY; } diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp index 39cbd29bf..98cfc28c0 100644 --- a/lib/network/NetworkServer.cpp +++ b/lib/network/NetworkServer.cpp @@ -27,7 +27,7 @@ void NetworkServer::start(uint16_t port) void NetworkServer::startAsyncAccept() { - std::shared_ptr upcomingConnection = std::make_shared(*io); + auto upcomingConnection = std::make_shared(*io); acceptor->async_accept(*upcomingConnection, std::bind(&NetworkServer::connectionAccepted, this, upcomingConnection, _1)); } diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 691dd417c..a8270a069 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -19,7 +19,7 @@ static const auto accountCookieLifetime = std::chrono::hours(24 * 7); -bool LobbyServer::isAccountNameValid(const std::string & accountName) +bool LobbyServer::isAccountNameValid(const std::string & accountName) const { if(accountName.size() < 4) return false; @@ -40,7 +40,7 @@ std::string LobbyServer::sanitizeChatMessage(const std::string & inputString) co return inputString; } -NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID) +NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID) const { for(const auto & account : activeAccounts) if(account.second.accountID == accountID) @@ -49,7 +49,7 @@ NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID) return nullptr; } -NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) +NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) const { for(const auto & account : activeGameRooms) if(account.second.roomID == gameRoomID) @@ -187,7 +187,7 @@ void LobbyServer::sendJoinRoomSuccess(const NetworkConnectionPtr & target, const sendMessage(target, reply); } -void LobbyServer::sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & accountID, std::string & displayName, const std::string & messageText) +void LobbyServer::sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & accountID, const std::string & displayName, const std::string & messageText) { JsonNode reply; reply["type"].String() = "chatMessage"; @@ -301,8 +301,8 @@ void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection database->insertChatMessage(accountID, "global", "english", messageText); - for(const auto & connection : activeAccounts) - sendChatMessage(connection.first, "global", "english", accountID, displayName, messageText); + for(const auto & otherConnection : activeAccounts) + sendChatMessage(otherConnection.first, "global", "english", accountID, displayName, messageText); } void LobbyServer::receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json) @@ -548,7 +548,7 @@ void LobbyServer::receiveDeclineInvite(const NetworkConnectionPtr & connection, LobbyServer::~LobbyServer() = default; LobbyServer::LobbyServer(const boost::filesystem::path & databasePath) - : database(new LobbyDatabase(databasePath)) + : database(std::make_unique(databasePath)) , networkHandler(INetworkHandler::createHandler()) , networkServer(networkHandler->createServerTCP(*this)) { diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index 640cad245..e70d2e9db 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -56,10 +56,10 @@ class LobbyServer : public INetworkServerListener std::unique_ptr networkServer; std::string sanitizeChatMessage(const std::string & inputString) const; - bool isAccountNameValid(const std::string & accountName); + bool isAccountNameValid(const std::string & accountName) const; - NetworkConnectionPtr findAccount(const std::string & accountID); - NetworkConnectionPtr findGameRoom(const std::string & gameRoomID); + NetworkConnectionPtr findAccount(const std::string & accountID) const; + NetworkConnectionPtr findGameRoom(const std::string & gameRoomID) const; void onNewConnection(const NetworkConnectionPtr & connection) override; void onDisconnected(const NetworkConnectionPtr & connection) override; @@ -70,7 +70,7 @@ class LobbyServer : public INetworkServerListener void broadcastActiveAccounts(); void broadcastActiveGameRooms(); - void sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & accountID, std::string & displayName, const std::string & messageText); + void sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & accountID, const std::string & displayName, const std::string & messageText); void sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie); void sendLoginFailed(const NetworkConnectionPtr & target, const std::string & reason); void sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie, const std::string & displayName); diff --git a/lobby/SQLiteConnection.cpp b/lobby/SQLiteConnection.cpp index afa8926b9..439ad839b 100644 --- a/lobby/SQLiteConnection.cpp +++ b/lobby/SQLiteConnection.cpp @@ -32,8 +32,7 @@ SQLiteStatement::SQLiteStatement(SQLiteInstance & instance, sqlite3_stmt * state SQLiteStatement::~SQLiteStatement() { - int result = sqlite3_finalize(m_statement); - checkSQLiteError(m_instance.m_connection, result); + sqlite3_finalize(m_statement); } bool SQLiteStatement::execute() @@ -179,8 +178,7 @@ SQLiteInstance::SQLiteInstance(sqlite3 * connection) SQLiteInstance::~SQLiteInstance() { - int result = sqlite3_close(m_connection); - checkSQLiteError(m_connection, result); + sqlite3_close(m_connection); } SQLiteStatementPtr SQLiteInstance::prepare(const std::string & sql_text) diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index ca7d5003f..bc01e2619 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -117,7 +117,11 @@ public: }; CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts) - : state(EServerState::LOBBY), cmdLineOptions(opts), currentClientId(1), currentPlayerId(1), restartGameplay(false) + : restartGameplay(false) + , state(EServerState::LOBBY) + , currentClientId(1) + , currentPlayerId(1) + , cmdLineOptions(opts) { uuid = boost::uuids::to_string(boost::uuids::random_generator()()); logNetwork->trace("CVCMIServer created! UUID: %s", uuid); @@ -401,7 +405,7 @@ bool CVCMIServer::passHost(int toConnectionId) return false; } -void CVCMIServer::clientConnected(std::shared_ptr c, std::vector & names, std::string uuid, EStartMode mode) +void CVCMIServer::clientConnected(std::shared_ptr c, std::vector & names, const std::string & uuid, EStartMode mode) { if(state != EServerState::LOBBY) throw std::runtime_error("CVCMIServer::clientConnected called while game is not accepting clients!"); diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index a7da4028e..2b47ab480 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -105,7 +105,7 @@ public: void setPlayerConnectedId(PlayerSettings & pset, ui8 player) const; void updateStartInfoOnMapChange(std::shared_ptr mapInfo, std::shared_ptr mapGenOpt = {}); - void clientConnected(std::shared_ptr c, std::vector & names, std::string uuid, EStartMode mode); + void clientConnected(std::shared_ptr c, std::vector & names, const std::string & uuid, EStartMode mode); void clientDisconnected(std::shared_ptr c); void reconnectPlayer(int connId); diff --git a/server/EntryPoint.cpp b/server/EntryPoint.cpp index d0f154c22..145027801 100644 --- a/server/EntryPoint.cpp +++ b/server/EntryPoint.cpp @@ -24,8 +24,8 @@ #include -std::string SERVER_NAME_AFFIX = "server"; -std::string SERVER_NAME = GameConstants::VCMI_VERSION + std::string(" (") + SERVER_NAME_AFFIX + ')'; +const std::string SERVER_NAME_AFFIX = "server"; +const std::string SERVER_NAME = GameConstants::VCMI_VERSION + std::string(" (") + SERVER_NAME_AFFIX + ')'; static void handleCommandOptions(int argc, const char * argv[], boost::program_options::variables_map & options) { @@ -110,7 +110,7 @@ int main(int argc, const char * argv[]) logConfig.configure(); loadDLLClasses(); - srand((ui32)time(nullptr)); + std::srand(static_cast(time(nullptr))); { CVCMIServer server(opts); diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index 921f76108..1f2aa6053 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -142,7 +142,7 @@ public: si.mapname = "anything";//does not matter, map service mocked si.difficulty = 0; si.mapfileChecksum = 0; - si.mode = StartInfo::NEW_GAME; + si.mode = EStartMode::NEW_GAME; si.seedToBeUsed = 42; std::unique_ptr header = mapService.loadMapHeader(ResourcePath(si.mapname)); From 6d2ca070ea2961d76d468148a8071c7758fc7cf4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 27 Jan 2024 23:41:36 +0200 Subject: [PATCH 41/98] Show number of players in open room --- lobby/LobbyDatabase.cpp | 19 +++++++++++++++++-- lobby/LobbyDatabase.h | 1 + lobby/LobbyServer.cpp | 2 ++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lobby/LobbyDatabase.cpp b/lobby/LobbyDatabase.cpp index c4a40aa13..44a747a68 100644 --- a/lobby/LobbyDatabase.cpp +++ b/lobby/LobbyDatabase.cpp @@ -170,12 +170,18 @@ void LobbyDatabase::prepareStatements() )"; static const std::string getActiveGameRoomsText = R"( - SELECT roomID, hostAccountID, displayName, status, 0, playerLimit + SELECT roomID, hostAccountID, displayName, status, playerLimit FROM gameRooms LEFT JOIN accounts ON hostAccountID = accountID WHERE status = 1 )"; + static const std::string countAccountsInRoomText = R"( + SELECT COUNT(accountID) + FROM gameRoomPlayers + WHERE roomID = ? + )"; + static const std::string getAccountDisplayNameText = R"( SELECT displayName FROM accounts @@ -239,6 +245,7 @@ void LobbyDatabase::prepareStatements() getActiveAccountsStatement = database->prepare(getActiveAccountsText); getActiveGameRoomsStatement = database->prepare(getActiveGameRoomsText); getAccountDisplayNameStatement = database->prepare(getAccountDisplayNameText); + countAccountsInRoomStatement = database->prepare(countAccountsInRoomText); isAccountCookieValidStatement = database->prepare(isAccountCookieValidText); isPlayerInGameRoomStatement = database->prepare(isPlayerInGameRoomText); @@ -429,10 +436,18 @@ std::vector LobbyDatabase::getActiveGameRooms() while(getActiveGameRoomsStatement->execute()) { LobbyGameRoom entry; - getActiveGameRoomsStatement->getColumns(entry.roomID, entry.hostAccountID, entry.hostAccountDisplayName, entry.roomStatus, entry.playersCount, entry.playersLimit); + getActiveGameRoomsStatement->getColumns(entry.roomID, entry.hostAccountID, entry.hostAccountDisplayName, entry.roomStatus, entry.playersLimit); result.push_back(entry); } getActiveGameRoomsStatement->reset(); + + for (auto & room : result) + { + countAccountsInRoomStatement->setBinds(room.roomID); + if(countAccountsInRoomStatement->execute()) + countAccountsInRoomStatement->getColumns(room.playersCount); + countAccountsInRoomStatement->reset(); + } return result; } diff --git a/lobby/LobbyDatabase.h b/lobby/LobbyDatabase.h index 898256020..ef940e3b5 100644 --- a/lobby/LobbyDatabase.h +++ b/lobby/LobbyDatabase.h @@ -41,6 +41,7 @@ class LobbyDatabase SQLiteStatementPtr getActiveAccountsStatement; SQLiteStatementPtr getAccountGameRoomStatement; SQLiteStatementPtr getAccountDisplayNameStatement; + SQLiteStatementPtr countAccountsInRoomStatement; SQLiteStatementPtr isAccountCookieValidStatement; SQLiteStatementPtr isGameRoomCookieValidStatement; diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index a8270a069..0b745ed6b 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -465,6 +465,7 @@ void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, c // TODO: additional flags / initial settings, e.g. allowCheats // TODO: connection mode: direct or proxy. For now direct is assumed. Proxy might be needed later, for hosted servers + database->insertPlayerIntoGameRoom(accountID, gameRoomID); broadcastActiveGameRooms(); sendJoinRoomSuccess(connection, gameRoomID); } @@ -493,6 +494,7 @@ void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, c if(database->getGameRoomFreeSlots(gameRoomID) == 0) return; + database->insertPlayerIntoGameRoom(accountID, gameRoomID); sendAccountJoinsRoom(targetRoom, accountID); //No reply to client - will be sent once match server establishes proxy connection with lobby From bed05eb52d27d21184594877689faa525ad873c5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 28 Jan 2024 00:04:54 +0200 Subject: [PATCH 42/98] WIP: Implemented joining public rooms --- client/globalLobby/GlobalLobbyWindow.cpp | 13 ++++----- lobby/LobbyDatabase.cpp | 35 ++++++++++++++++++++---- lobby/LobbyDatabase.h | 3 +- lobby/LobbyServer.cpp | 3 ++ 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/client/globalLobby/GlobalLobbyWindow.cpp b/client/globalLobby/GlobalLobbyWindow.cpp index a4a9a67c3..8aa54378e 100644 --- a/client/globalLobby/GlobalLobbyWindow.cpp +++ b/client/globalLobby/GlobalLobbyWindow.cpp @@ -51,23 +51,20 @@ void GlobalLobbyWindow::doSendChatMessage() void GlobalLobbyWindow::doCreateGameRoom() { GH.windows().createAndPushWindow(); - // TODO: - // start local server and supply our UUID / client credentials to it - // server logs into lobby ( uuid = client, mode = server ). This creates 'room' in mode 'empty' - // server starts accepting connections from players (including host) - // client connects to local server - // client sends createGameRoom query to lobby with own / server UUID and mode 'direct' (non-proxy) - // client requests to change room status to private or public } void GlobalLobbyWindow::doInviteAccount(const std::string & accountID) { - + assert(0); // TODO } void GlobalLobbyWindow::doJoinRoom(const std::string & roomID) { + JsonNode toSend; + toSend["type"].String() = "joinGameRoom"; + toSend["gameRoomID"].String() = roomID; + CSH->getGlobalLobby().sendMessage(toSend); } void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when) diff --git a/lobby/LobbyDatabase.cpp b/lobby/LobbyDatabase.cpp index 44a747a68..e06b95ddc 100644 --- a/lobby/LobbyDatabase.cpp +++ b/lobby/LobbyDatabase.cpp @@ -176,12 +176,18 @@ void LobbyDatabase::prepareStatements() WHERE status = 1 )"; - static const std::string countAccountsInRoomText = R"( + static const std::string countRoomUsedSlotsText = R"( SELECT COUNT(accountID) FROM gameRoomPlayers WHERE roomID = ? )"; + static const std::string countRoomTotalSlotsText = R"( + SELECT playerLimit + FROM gameRooms + WHERE roomID = ? + )"; + static const std::string getAccountDisplayNameText = R"( SELECT displayName FROM accounts @@ -245,7 +251,8 @@ void LobbyDatabase::prepareStatements() getActiveAccountsStatement = database->prepare(getActiveAccountsText); getActiveGameRoomsStatement = database->prepare(getActiveGameRoomsText); getAccountDisplayNameStatement = database->prepare(getAccountDisplayNameText); - countAccountsInRoomStatement = database->prepare(countAccountsInRoomText); + countRoomUsedSlotsStatement = database->prepare(countRoomUsedSlotsText); + countRoomTotalSlotsStatement = database->prepare(countRoomTotalSlotsText); isAccountCookieValidStatement = database->prepare(isAccountCookieValidText); isPlayerInGameRoomStatement = database->prepare(isPlayerInGameRoomText); @@ -404,6 +411,22 @@ LobbyRoomState LobbyDatabase::getGameRoomStatus(const std::string & roomID) uint32_t LobbyDatabase::getGameRoomFreeSlots(const std::string & roomID) { + uint32_t usedSlots = 0; + uint32_t totalSlots = 0; + + countRoomUsedSlotsStatement->setBinds(roomID); + if(countRoomUsedSlotsStatement->execute()) + countRoomUsedSlotsStatement->getColumns(usedSlots); + countRoomUsedSlotsStatement->reset(); + + countRoomTotalSlotsStatement->setBinds(roomID); + if(countRoomTotalSlotsStatement->execute()) + countRoomTotalSlotsStatement->getColumns(totalSlots); + countRoomTotalSlotsStatement->reset(); + + + if (totalSlots > usedSlots) + return totalSlots - usedSlots; return 0; } @@ -443,10 +466,10 @@ std::vector LobbyDatabase::getActiveGameRooms() for (auto & room : result) { - countAccountsInRoomStatement->setBinds(room.roomID); - if(countAccountsInRoomStatement->execute()) - countAccountsInRoomStatement->getColumns(room.playersCount); - countAccountsInRoomStatement->reset(); + countRoomUsedSlotsStatement->setBinds(room.roomID); + if(countRoomUsedSlotsStatement->execute()) + countRoomUsedSlotsStatement->getColumns(room.playersCount); + countRoomUsedSlotsStatement->reset(); } return result; } diff --git a/lobby/LobbyDatabase.h b/lobby/LobbyDatabase.h index ef940e3b5..ae44537a4 100644 --- a/lobby/LobbyDatabase.h +++ b/lobby/LobbyDatabase.h @@ -41,7 +41,8 @@ class LobbyDatabase SQLiteStatementPtr getActiveAccountsStatement; SQLiteStatementPtr getAccountGameRoomStatement; SQLiteStatementPtr getAccountDisplayNameStatement; - SQLiteStatementPtr countAccountsInRoomStatement; + SQLiteStatementPtr countRoomUsedSlotsStatement; + SQLiteStatementPtr countRoomTotalSlotsStatement; SQLiteStatementPtr isAccountCookieValidStatement; SQLiteStatementPtr isGameRoomCookieValidStatement; diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 0b745ed6b..6f05aa708 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -485,6 +485,9 @@ void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, c auto roomStatus = database->getGameRoomStatus(gameRoomID); + if(roomStatus != LobbyRoomState::PRIVATE && roomStatus != LobbyRoomState::PUBLIC) + return; + if(roomStatus == LobbyRoomState::PRIVATE) { if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED) From 131e5600cd700859e5b72312f1d38056575d32d8 Mon Sep 17 00:00:00 2001 From: kdmcser Date: Mon, 29 Jan 2024 23:26:04 +0800 Subject: [PATCH 43/98] update Chinese translation --- Mods/vcmi/config/vcmi/chinese.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Mods/vcmi/config/vcmi/chinese.json b/Mods/vcmi/config/vcmi/chinese.json index dca5f2bdd..85f2f85b3 100644 --- a/Mods/vcmi/config/vcmi/chinese.json +++ b/Mods/vcmi/config/vcmi/chinese.json @@ -20,6 +20,7 @@ "vcmi.adventureMap.playerAttacked" : "玩家遭受攻击: %s", "vcmi.adventureMap.moveCostDetails" : "移动点数 - 花费: %TURNS 轮 + %POINTS 点移动力, 剩余移动力: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "移动点数 - 花费: %POINTS 点移动力, 剩余移动力: %REMAINING", + "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "抱歉, 重放对手回合功能目前还未实现!", "vcmi.capitalColors.0" : "红色", "vcmi.capitalColors.1" : "蓝色", @@ -70,7 +71,9 @@ "vcmi.lobby.mapPreview" : "地图预览", "vcmi.lobby.noPreview" : "无地上部分", "vcmi.lobby.noUnderground" : "无地下部分", + "vcmi.lobby.sortDate" : "按修改时间排序地图", + "vcmi.client.errors.invalidMap" : "{无效的地图或战役}\n\n启动游戏失败!选定的地图或战役格式不正确或已损坏。原因:\n%s", "vcmi.client.errors.missingCampaigns" : "{找不到数据文件}\n\n没有找到战役数据文件!你可能使用了不完整或损坏的英雄无敌3数据文件,请重新安装数据文件。", "vcmi.server.errors.existingProcess" : "一个VCMI进程已经在运行,启动新进程前请结束它。", "vcmi.server.errors.modsToEnable" : "{需要启用的mod列表}", @@ -144,6 +147,8 @@ "vcmi.adventureOptions.mapScrollSpeed1.help": "将地图卷动速度设置为非常慢", "vcmi.adventureOptions.mapScrollSpeed5.help": "将地图卷动速度设置为非常快", "vcmi.adventureOptions.mapScrollSpeed6.help": "将地图卷动速度设置为即刻。", + "vcmi.adventureOptions.hideBackground.hover" : "隐藏背景", + "vcmi.adventureOptions.hideBackground.help" : "{隐藏背景}\n\n将冒险地图背景隐藏并使用一个纹理替代。", "vcmi.battleOptions.queueSizeLabel.hover": "回合顺序指示器", "vcmi.battleOptions.queueSizeNoneButton.hover": "关闭", @@ -183,6 +188,10 @@ "vcmi.battleWindow.damageEstimation.damage.1" : "%d 伤害", "vcmi.battleWindow.damageEstimation.kills" : "%d 将被消灭", "vcmi.battleWindow.damageEstimation.kills.1" : "%d 将被消灭", + "vcmi.battleWindow.killed" : "被击杀", + "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s 被精准射击击杀!", + "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s 被精准射击击杀!", + "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s 被精准射击击杀!", "vcmi.battleResultsWindow.applyResultsLabel" : "接受战斗结果", @@ -225,6 +234,8 @@ "vcmi.heroWindow.openBackpack.hover" : "开启宝物背包界面", "vcmi.heroWindow.openBackpack.help" : "用更大的界面显示所有获得的宝物", + "vcmi.tavernWindow.inviteHero" : "邀请英雄", + "vcmi.commanderWindow.artifactMessage" : "你要把这个宝物还给英雄吗?", "vcmi.creatureWindow.showBonuses.hover" : "属性视图", @@ -374,6 +385,8 @@ "core.bonus.ENCHANTER.description": "每回合群体施放${subtype.spell}", "core.bonus.ENCHANTED.name": "法术加持", "core.bonus.ENCHANTED.description": "永久处于${subtype.spell}影响", + "core.bonus.ENEMY_ATTACK_REDUCTION.name": "忽略攻击 (${val}%)", + "core.bonus.ENEMY_ATTACK_REDUCTION.description": "当被攻击时,攻击方${val}%的攻击力将被无视。", "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "忽略防御 (${val}%)", "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "当攻击时,目标生物${val}%的防御力将被无视。", "core.bonus.FIRE_IMMUNITY.name": "火系免疫", @@ -386,6 +399,8 @@ "core.bonus.FEAR.description": "使得敌方一只部队恐惧", "core.bonus.FEARLESS.name": "无惧", "core.bonus.FEARLESS.description": "免疫恐惧特质", + "core.bonus.FEROCITY.name": "残暴", + "core.bonus.FEROCITY.description": "如果杀死一个生物,额外攻击${val}次", "core.bonus.FLYING.name": "飞行兵种", "core.bonus.FLYING.description": "以飞行的方式移动(无视障碍)", "core.bonus.FREE_SHOOTING.name": "近身射击", @@ -440,6 +455,8 @@ "core.bonus.REBIRTH.description": "当整支部队死亡后${val}%会复活", "core.bonus.RETURN_AFTER_STRIKE.name": "攻击后返回", "core.bonus.RETURN_AFTER_STRIKE.description": "近战攻击后回到初始位置", + "core.bonus.REVENGE.name": "复仇", + "core.bonus.REVENGE.description": "造成额外伤害,其数量基于攻击者在战斗中损失的生命值", "core.bonus.SHOOTER.name": "远程攻击", "core.bonus.SHOOTER.description": "生物可以射击", "core.bonus.SHOOTS_ALL_ADJACENT.name": "范围远程攻击", From c5aa31f46ac6580c74d3e2340fab7c8f4009c083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Tue, 30 Jan 2024 18:04:15 +0100 Subject: [PATCH 44/98] First sketch --- cmake_modules/VCMI_lib.cmake | 2 + lib/rmg/PenroseTiling.cpp | 227 +++++++++++++++++++++++++++++++++++ lib/rmg/PenroseTiling.h | 57 +++++++++ 3 files changed, 286 insertions(+) create mode 100644 lib/rmg/PenroseTiling.cpp create mode 100644 lib/rmg/PenroseTiling.h diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 39b8044f5..62218f98b 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -151,6 +151,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/rmg/Zone.cpp ${MAIN_LIB_DIR}/rmg/Functions.cpp ${MAIN_LIB_DIR}/rmg/RmgMap.cpp + ${MAIN_LIB_DIR}/rmg/PenroseTiling.cpp ${MAIN_LIB_DIR}/rmg/modificators/Modificator.cpp ${MAIN_LIB_DIR}/rmg/modificators/ObjectManager.cpp ${MAIN_LIB_DIR}/rmg/modificators/ObjectDistributor.cpp @@ -522,6 +523,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/rmg/RmgMap.h ${MAIN_LIB_DIR}/rmg/float3.h ${MAIN_LIB_DIR}/rmg/Functions.h + ${MAIN_LIB_DIR}/rmg/PenroseTiling.h ${MAIN_LIB_DIR}/rmg/modificators/Modificator.h ${MAIN_LIB_DIR}/rmg/modificators/ObjectManager.h ${MAIN_LIB_DIR}/rmg/modificators/ObjectDistributor.h diff --git a/lib/rmg/PenroseTiling.cpp b/lib/rmg/PenroseTiling.cpp new file mode 100644 index 000000000..2af1d7b71 --- /dev/null +++ b/lib/rmg/PenroseTiling.cpp @@ -0,0 +1,227 @@ +/* + * © 2020 Michael Percival + * See LICENSE file for copyright and license details. + */ + +// Adapted from https://github.com/mpizzzle/penrose + +// FIXME: Find library for geometry representation +//https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/geometry/reference/adapted/boost_polygon/point_data.html + +//#include +//#include + +//#include +//#include + +#include "StdInc.h" + +#include +#include +#include +#include + +#include +//https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/geometry/reference/strategies/strategy_transform_rotate_transformer.html + +//#include "shader.hpp" +//#include "png_writer.hpp" + +#include "PenroseTiling.h" + +VCMI_LIB_NAMESPACE_BEGIN + +//static const std::string file_name = "penrose.png"; + +Triangle::Triangle(bool t_123, const TIndices & inds): + tiling(t_123), + indices(inds) +{} + +void PenroseTiling::split(Triangle& p, std::vector& points, + std::array, 5>& indices, uint32_t depth) +{ + uint32_t s = points.size(); + TIndices& i = p.indices; + + const auto p2 = P2; + + if (depth > 0) + { + if (p.tiling ^ !p2) + { + points.push_back(glm::vec2(((1.0f - PHI) * points[i[0]]) + (PHI * points[i[2]]))); + points.push_back(glm::vec2(((1.0f - PHI) * points[i[p2]]) + (PHI * points[i[!p2]]))); + + Triangle t1(p2, TIndices({ i[(!p2) + 1], p2 ? i[2] : s, p2 ? s : i[1] })); + Triangle t2(true, TIndices({ p2 ? i[1] : s, s + 1, p2 ? s : i[1] })); + Triangle t3(false, TIndices({ s, s + 1, i[0] })); + + // FIXME: Make sure these are not destroyed when we leave the scope + p.subTriangles = { &t1, &t2, &t3 }; + } + else + { + points.push_back(glm::vec2(((1.0f - PHI) * points[i[p2 * 2]]) + (PHI * points[i[!p2]]))); + + Triangle t1(true, TIndices({ i[2], s, i[1] })); + Triangle t2(false, TIndices({ i[(!p2) + 1], s, i[0] })); + + p.subTriangles = { &t1, &t2 }; + } + + for (auto& t : p.subTriangles) + { + if (depth == 1) + { + for (uint32_t k = 0; k < 3; ++k) + { + if (k != (t->tiling ^ !p2 ? 2 : 1)) + { + indices[indices.size() - 1].push_back(t->indices[k]); + indices[indices.size() - 1].push_back(t->indices[((k + 1) % 3)]); + } + } + + indices[t->tiling + (p.tiling ? 0 : 2)].insert(indices[t->tiling + (p.tiling ? 0 : 2)].end(), t->indices.begin(), t->indices.end()); + } + + // Split recursively + split(*t, points, indices, depth - 1); + } + } + + return; +} + +// TODO: Return something +void generatePenroseTiling(size_t numZones, CRandomGenerator * rand); +{ + float scale = 100.f / (numZones + 20); //TODO: Use it to initialize the large tile + + //static std::default_random_engine e(std::random_device{}()); + static std::uniform_real_distribution<> d(0, 1); + + /* + std::vector colours = { glm::vec3(d(e), d(e), d(e)), glm::vec3(d(e), d(e), d(e)), + glm::vec3(d(e), d(e), d(e)), glm::vec3(d(e), d(e), d(e)), + glm::vec3(d(e), d(e), d(e)), glm::vec3(d(e), d(e), d(e)) }; + */ + + float polyAngle = glm::radians(360.0f / POLY); + + std::vector points = { glm::vec2(0.0f, 0.0f), glm::vec2(0.0f, 1.0f) }; + std::array, 5> indices; + + for (uint32_t i = 1; i < POLY; ++i) + { + //TODO: Use boost to rotate + glm::vec2 next = glm::rotate(points[i], polyAngle); + points.push_back(next); + } + + // TODO: Scale to unit square + for (auto& p : points) + { + p.x = (p.x / window_w) * window_h; + } + + for (uint32_t i = 0; i < POLY; i++) + { + std::array p = { (i % (POLY + 1)) + 1, ((i + 1) % POLY) + 1 }; + + triangle t(true, TIndices({ 0, p[i & 1], p[!(i & 1)] })); + + split(t, points, indices, DEPTH); + } + + // TODO: Return collection of triangles + // TODO: Get center point of the triangle + + // Do not draw anything + /* + if(!glfwInit()) + { + return -1; + } + + glfwWindowHint(GLFW_SAMPLES, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + GLFWwindow* window = glfwCreateWindow(window_w, window_h, "penrose", NULL, NULL); + + if(window == NULL) { + glfwTerminate(); + return -1; + } + + glfwMakeContextCurrent(window); + glewExperimental=true; + + if (glewInit() != GLEW_OK) { + return -1; + } + + glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE); + + uint32_t VAOs[5], VBO, EBOs[5]; + + glGenVertexArrays(5, VAOs); + glGenBuffers(1, &VBO); + glGenBuffers(5, EBOs); + glLineWidth(line_w); + + for (uint32_t i = 0; i < indices.size(); ++i) { + glBindVertexArray(VAOs[i]); + + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferData(GL_ARRAY_BUFFER, points.size() * 4 * 2, &points[0], GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBOs[i]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices[i].size() * 4, &indices[i][0], GL_STATIC_DRAW); + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + } + + uint32_t programID = Shader::loadShaders("vertex.vert", "fragment.frag"); + GLint paint = glGetUniformLocation(programID, "paint"); + + while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose(window) == 0 && paint != -1) { + glViewport(-1.0 * (window_w / scale) * ((0.5 * scale) - 0.5), -1.0 * (window_h / scale) * ((0.5 * scale) - 0.5), window_w, window_h); + glClearColor(colours.back().x, colours.back().y, colours.back().z, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glUseProgram(programID); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + for (uint32_t i = 0; i < indices.size(); ++i) { + glPolygonMode(GL_FRONT_AND_BACK, i < indices.size() - 1 ? GL_FILL : GL_LINE); + glUniform3fv(paint, 1, &colours[i][0]); + glBindVertexArray(VAOs[i]); + glDrawElements(i < indices.size() - 1 ? GL_TRIANGLES : GL_LINES, indices[i].size(), GL_UNSIGNED_INT, 0); + } + + glfwSwapBuffers(window); + glfwPollEvents(); + } + + int frame_w, frame_h; + glfwGetFramebufferSize(window, &frame_w, &frame_h); + + png_bytep* row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * frame_h); + + for (int y = 0; y < frame_h; ++y) { + row_pointers[y] = (png_byte*) malloc((4 * sizeof(png_byte)) * frame_w); + glReadPixels(0, y, frame_w, 1, GL_RGBA, GL_UNSIGNED_BYTE, row_pointers[y]); + } + + PngWriter::write_png_file(file_name, frame_w, frame_h, row_pointers); + + return 0; + */ +} + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/PenroseTiling.h b/lib/rmg/PenroseTiling.h new file mode 100644 index 000000000..cc3060a8a --- /dev/null +++ b/lib/rmg/PenroseTiling.h @@ -0,0 +1,57 @@ +/* + * PenroseTiling.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "../GameConstants.h" +#include "../CRandomGenerator.h" +#include +#include + + +VCMI_LIB_NAMESPACE_BEGIN + +typedef std::array TIndices; + +class Triangle +{ +public: + const bool tiling; + TIndices indices; + + std::vector subTriangles; + + Triangle(bool t_123, const TIndices & inds); +}; + +class PenroseTiling +{ + +public: + const float PHI = 1.0 / ((1.0 + std::sqrt(5.0)) / 2); + // TODO: Is that the number of symmetries? + const uint32_t POLY = 10; + + const float scale = 4.0f; + //const uint32_t window_w = 1920 * scale; + //const uint32_t window_h = 1080 * scale; + const uint32_t DEPTH = 10; //recursion depth + + const bool P2 = false; // Tiling type + //const float line_w = 2.0f; //line width + + void generatePenroseTiling(size_t numZones, CRandomGenerator * rand); + +private: + void split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth); + +}; + +VCMI_LIB_NAMESPACE_END \ No newline at end of file From c5c46a7c9a8585671d4b1d38e3f644c19a5ed033 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 29 Jan 2024 22:05:11 +0200 Subject: [PATCH 45/98] Implemented connecting to server via proxy --- client/CServerHandler.cpp | 17 +++++-- client/CServerHandler.h | 17 +++++-- client/NetPacksLobbyClient.cpp | 1 + client/globalLobby/GlobalLobbyClient.cpp | 33 +++++++++++++- client/globalLobby/GlobalLobbyClient.h | 2 + client/globalLobby/GlobalLobbyServerSetup.cpp | 4 +- client/mainmenu/CMainMenu.cpp | 4 +- config/schemas/settings.json | 13 +++--- lib/network/NetworkServer.cpp | 9 +++- lobby/LobbyDatabase.cpp | 28 +++++++++--- lobby/LobbyDatabase.h | 1 + lobby/LobbyServer.cpp | 37 ++++++++++----- lobby/LobbyServer.h | 4 +- server/GlobalLobbyProcessor.cpp | 45 ++++++++++++------- server/GlobalLobbyProcessor.h | 6 +-- 15 files changed, 160 insertions(+), 61 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index bf0a7b27b..9490a97d9 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -139,6 +139,7 @@ CServerHandler::CServerHandler() , state(EClientState::NONE) , campaignStateToSend(nullptr) , screenType(ESelectionScreen::unknown) + , serverMode(EServerMode::NONE) , loadMode(ELoadMode::NONE) , client(nullptr) , campaignServerRestartLock(false) @@ -156,10 +157,11 @@ void CServerHandler::threadRunNetwork() logGlobal->info("Ending network thread"); } -void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen, const std::vector & names) +void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode newServerMode, const std::vector & names) { hostClientId = -1; state = EClientState::NONE; + serverMode = newServerMode; mapToStart = nullptr; th = std::make_unique(); c.reset(); @@ -297,11 +299,18 @@ void CServerHandler::onTimer() networkHandler->connectToRemote(*this, getLocalHostname(), getLocalPort()); } -void CServerHandler::onConnectionEstablished(const std::shared_ptr & netConnection) +void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netConnection) { networkConnection = netConnection; logNetwork->info("Connection established"); + + if (serverMode == EServerMode::LOBBY_GUEST) + { + // say hello to lobby to switch connection to proxy mode + getGlobalLobby().sendProxyConnectionLogin(netConnection); + } + c = std::make_shared(netConnection); nextClient = std::make_unique(); c->uuid = uuid; @@ -791,12 +800,12 @@ void CServerHandler::debugStartTest(std::string filename, bool save) auto mapInfo = std::make_shared(); if(save) { - resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, {}); + resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, EServerMode::LOCAL, {}); mapInfo->saveInit(ResourcePath(filename, EResType::SAVEGAME)); } else { - resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, {}); + resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, EServerMode::LOCAL, {}); mapInfo->mapInit(filename); } if(settings["session"]["donotstartserver"].Bool()) diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 9733887cb..923a319b6 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -57,6 +57,14 @@ enum class EClientState : ui8 CONNECTION_FAILED // We could not connect to server }; +enum class EServerMode : uint8_t +{ + NONE = 0, + LOCAL, // no global lobby + LOBBY_HOST, // We are hosting global server available via global lobby + LOBBY_GUEST // Connecting to a remote server via proxy provided by global lobby +}; + class IServerAPI { protected: @@ -106,10 +114,10 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor void onServerFinished(); void sendLobbyPack(const CPackForLobby & pack) const override; - void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; + void onPacketReceived(const NetworkConnectionPtr &, const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; - void onConnectionEstablished(const std::shared_ptr &) override; - void onDisconnected(const std::shared_ptr &) override; + void onConnectionEstablished(const NetworkConnectionPtr &) override; + void onDisconnected(const NetworkConnectionPtr &) override; void onTimer() override; void applyPackOnLobbyScreen(CPackForLobby & pack); @@ -132,6 +140,7 @@ public: std::shared_ptr campaignStateToSend; ESelectionScreen screenType; // To create lobby UI only after server is setup + EServerMode serverMode; ELoadMode loadMode; // For saves filtering in SelectionTab //////////////////// @@ -146,7 +155,7 @@ public: CServerHandler(); ~CServerHandler(); - void resetStateForLobby(EStartMode mode, ESelectionScreen screen, const std::vector & names); + void resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode serverMode, const std::vector & names); void startLocalServerAndConnect(bool connectToLobby); void connectToServer(const std::string & addr, const ui16 port); diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index 83dd2ce5b..eea298089 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -53,6 +53,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon if (!GH.windows().findWindows().empty()) { + assert(handler.serverMode == EServerMode::LOBBY_HOST); // announce opened game room // TODO: find better approach? int roomType = settings["lobby"]["roomType"].Integer(); diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index 0a0b215cb..a5fc9dc5e 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -18,6 +18,7 @@ #include "../gui/WindowHandler.h" #include "../windows/InfoWindows.h" #include "../CServerHandler.h" +#include "../mainmenu/CMainMenu.h" #include "../../lib/CConfigHandler.h" #include "../../lib/MetaString.h" @@ -190,7 +191,18 @@ void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json) void GlobalLobbyClient::receiveJoinRoomSuccess(const JsonNode & json) { - // TODO: store "gameRoomID" field and use it for future queries + Settings configRoom = settings.write["lobby"]["roomID"]; + configRoom->String() = json["gameRoomID"].String(); + + if (json["proxyMode"].Bool()) + { + CSH->resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, EServerMode::LOBBY_GUEST, {}); + CSH->loadMode = ELoadMode::MULTI; + + std::string hostname = settings["lobby"]["hostname"].String(); + int16_t port = settings["lobby"]["port"].Integer(); + CSH->connectToServer(hostname, port); + } } void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr & connection) @@ -334,3 +346,22 @@ void GlobalLobbyClient::activateInterface() else GH.windows().pushWindow(createLoginWindow()); } + +void GlobalLobbyClient::sendProxyConnectionLogin(const NetworkConnectionPtr & netConnection) +{ + JsonNode toSend; + toSend["type"].String() = "clientProxyLogin"; + toSend["accountID"] = settings["lobby"]["accountID"]; + toSend["accountCookie"] = settings["lobby"]["accountCookie"]; + toSend["gameRoomID"] = settings["lobby"]["roomID"]; + + std::string payloadString = toSend.toJson(true); + + // FIXME: find better approach + uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); + uint8_t * payloadEnd = payloadBegin + payloadString.size(); + + std::vector payloadBuffer(payloadBegin, payloadEnd); + + netConnection->sendPacket(payloadBuffer); +} diff --git a/client/globalLobby/GlobalLobbyClient.h b/client/globalLobby/GlobalLobbyClient.h index c6a80fa8a..3e1a32dd3 100644 --- a/client/globalLobby/GlobalLobbyClient.h +++ b/client/globalLobby/GlobalLobbyClient.h @@ -63,6 +63,8 @@ public: void sendOpenPublicRoom(); void sendOpenPrivateRoom(); + void sendProxyConnectionLogin(const NetworkConnectionPtr & netConnection); + void connect(); bool isConnected(); }; diff --git a/client/globalLobby/GlobalLobbyServerSetup.cpp b/client/globalLobby/GlobalLobbyServerSetup.cpp index 5a5fa6f05..8b64e2377 100644 --- a/client/globalLobby/GlobalLobbyServerSetup.cpp +++ b/client/globalLobby/GlobalLobbyServerSetup.cpp @@ -125,9 +125,9 @@ void GlobalLobbyServerSetup::onGameModeChanged(int value) void GlobalLobbyServerSetup::onCreate() { if(toggleGameMode->getSelected() == 0) - CSH->resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, {}); + CSH->resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, EServerMode::LOBBY_HOST, {}); else - CSH->resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, {}); + CSH->resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, EServerMode::LOBBY_HOST, {}); CSH->loadMode = ELoadMode::MULTI; CSH->startLocalServerAndConnect(true); diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 113b80c31..254ce62d5 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -361,7 +361,7 @@ void CMainMenu::update() void CMainMenu::openLobby(ESelectionScreen screenType, bool host, const std::vector & names, ELoadMode loadMode) { - CSH->resetStateForLobby(screenType == ESelectionScreen::newGame ? EStartMode::NEW_GAME : EStartMode::LOAD_GAME, screenType, names); + CSH->resetStateForLobby(screenType == ESelectionScreen::newGame ? EStartMode::NEW_GAME : EStartMode::LOAD_GAME, screenType, EServerMode::LOCAL, names); CSH->loadMode = loadMode; GH.windows().createAndPushWindow(host); @@ -376,7 +376,7 @@ void CMainMenu::openCampaignLobby(const std::string & campaignFileName, std::str void CMainMenu::openCampaignLobby(std::shared_ptr campaign) { - CSH->resetStateForLobby(EStartMode::CAMPAIGN, ESelectionScreen::campaignList, {}); + CSH->resetStateForLobby(EStartMode::CAMPAIGN, ESelectionScreen::campaignList, EServerMode::LOCAL, {}); CSH->campaignStateToSend = campaign; GH.windows().createAndPushWindow(); } diff --git a/config/schemas/settings.json b/config/schemas/settings.json index daf7cb301..c394c7aaf 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -553,28 +553,24 @@ "type" : "object", "additionalProperties" : false, "default" : {}, - "required" : [ "mapPreview", "accountID", "accountCookie", "displayName", "hostname", "port", "roomPlayerLimit", "roomType", "roomMode" ], + "required" : [ "mapPreview", "accountID", "accountCookie", "displayName", "hostname", "port", "roomPlayerLimit", "roomType", "roomMode", "roomID" ], "properties" : { "mapPreview" : { "type" : "boolean", "default" : true }, - "accountID" : { "type" : "string", "default" : "" }, - "accountCookie" : { "type" : "string", "default" : "" }, - "displayName" : { "type" : "string", "default" : "" }, - "hostname" : { "type" : "string", "default" : "127.0.0.1" @@ -583,21 +579,22 @@ "type" : "number", "default" : 30303 }, - "roomPlayerLimit" : { "type" : "number", "default" : 2 }, - "roomType" : { "type" : "number", "default" : 0 }, - "roomMode" : { "type" : "number", "default" : 0 }, + "roomID" : { + "type" : "string", + "default" : "" + } } }, "gameTweaks" : { diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp index 98cfc28c0..316d78e09 100644 --- a/lib/network/NetworkServer.cpp +++ b/lib/network/NetworkServer.cpp @@ -53,15 +53,20 @@ void NetworkServer::sendPacket(const std::shared_ptr & conne void NetworkServer::closeConnection(const std::shared_ptr & connection) { + logNetwork->info("Closing connection!"); assert(connections.count(connection)); connections.erase(connection); } void NetworkServer::onDisconnected(const std::shared_ptr & connection) { + logNetwork->info("Connection lost!"); assert(connections.count(connection)); - connections.erase(connection); - listener.onDisconnected(connection); + if (connections.count(connection)) // how? Connection was explicitly closed before? + { + connections.erase(connection); + listener.onDisconnected(connection); + } } void NetworkServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) diff --git a/lobby/LobbyDatabase.cpp b/lobby/LobbyDatabase.cpp index e06b95ddc..13d97d177 100644 --- a/lobby/LobbyDatabase.cpp +++ b/lobby/LobbyDatabase.cpp @@ -155,6 +155,12 @@ void LobbyDatabase::prepareStatements() LIMIT 1 )"; + static const std::string getGameRoomStatusText = R"( + SELECT status + FROM gameRooms + WHERE roomID = ? + )"; + static const std::string getAccountGameRoomText = R"( SELECT grp.roomID FROM gameRoomPlayers grp @@ -209,14 +215,16 @@ void LobbyDatabase::prepareStatements() static const std::string isPlayerInGameRoomText = R"( SELECT COUNT(accountID) - FROM gameRoomPlayers - WHERE accountID = ? AND roomID = ? + FROM gameRoomPlayers grp + LEFT JOIN gameRooms gr ON gr.roomID = grp.roomID + WHERE accountID = ? AND grp.roomID = ? AND status IN (1, 2) )"; static const std::string isPlayerInAnyGameRoomText = R"( SELECT COUNT(accountID) - FROM gameRoomPlayers - WHERE accountID = ? + FROM gameRoomPlayers grp + LEFT JOIN gameRooms gr ON gr.roomID = grp.roomID + WHERE accountID = ? AND status IN (1, 2) )"; static const std::string isAccountIDExistsText = R"( @@ -247,6 +255,7 @@ void LobbyDatabase::prepareStatements() getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText); getIdleGameRoomStatement = database->prepare(getIdleGameRoomText); + getGameRoomStatusStatement = database->prepare(getGameRoomStatusText); getAccountGameRoomStatement = database->prepare(getAccountGameRoomText); getActiveAccountsStatement = database->prepare(getActiveAccountsText); getActiveGameRoomsStatement = database->prepare(getActiveGameRoomsText); @@ -406,7 +415,16 @@ LobbyInviteStatus LobbyDatabase::getAccountInviteStatus(const std::string & acco LobbyRoomState LobbyDatabase::getGameRoomStatus(const std::string & roomID) { - return {}; + int result = -1; + + getGameRoomStatusStatement->setBinds(roomID); + if(getGameRoomStatusStatement->execute()) + getGameRoomStatusStatement->getColumns(result); + getGameRoomStatusStatement->reset(); + + if (result != -1) + return static_cast(result); + return LobbyRoomState::CLOSED; } uint32_t LobbyDatabase::getGameRoomFreeSlots(const std::string & roomID) diff --git a/lobby/LobbyDatabase.h b/lobby/LobbyDatabase.h index ae44537a4..52a7ca5ee 100644 --- a/lobby/LobbyDatabase.h +++ b/lobby/LobbyDatabase.h @@ -37,6 +37,7 @@ class LobbyDatabase SQLiteStatementPtr getRecentMessageHistoryStatement; SQLiteStatementPtr getIdleGameRoomStatement; + SQLiteStatementPtr getGameRoomStatusStatement; SQLiteStatementPtr getActiveGameRoomsStatement; SQLiteStatementPtr getActiveAccountsStatement; SQLiteStatementPtr getAccountGameRoomStatement; diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 6f05aa708..ed774d85b 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -148,7 +148,7 @@ void LobbyServer::broadcastActiveAccounts() sendMessage(connection.first, reply); } -void LobbyServer::broadcastActiveGameRooms() +JsonNode LobbyServer::prepareActiveGameRooms() { auto activeGameRoomStats = database->getActiveGameRooms(); JsonNode reply; @@ -167,6 +167,13 @@ void LobbyServer::broadcastActiveGameRooms() reply["gameRooms"].Vector().push_back(jsonEntry); } + return reply; +} + +void LobbyServer::broadcastActiveGameRooms() +{ + auto reply = prepareActiveGameRooms(); + for(const auto & connection : activeAccounts) sendMessage(connection.first, reply); } @@ -179,11 +186,12 @@ void LobbyServer::sendAccountJoinsRoom(const NetworkConnectionPtr & target, cons sendMessage(target, reply); } -void LobbyServer::sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID) +void LobbyServer::sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID, bool proxyMode) { JsonNode reply; reply["type"].String() = "joinRoomSuccess"; reply["gameRoomID"].String() = gameRoomID; + reply["proxyMode"].Bool() = proxyMode; sendMessage(target, reply); } @@ -230,8 +238,9 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons { auto lockedPtr = activeProxies.at(connection).lock(); if(lockedPtr) - lockedPtr->sendPacket(message); - return; + return lockedPtr->sendPacket(message); + + throw std::runtime_error("Received unexpected message for inactive proxy!"); } JsonNode json(message.data(), message.size()); @@ -257,7 +266,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons if(json["type"].String() == "declineInvite") return receiveDeclineInvite(connection, json); - return; + throw std::runtime_error("Received unexpected message of type " + json["type"].String()); } // communication messages from vcmiserver @@ -266,7 +275,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons if(json["type"].String() == "leaveGameRoom") return receiveLeaveGameRoom(connection, json); - return; + throw std::runtime_error("Received unexpected message of type " + json["type"].String()); } // unauthorized connections - permit only login or register attempts @@ -287,6 +296,8 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons // TODO: add logging of suspicious connections. networkServer->closeConnection(connection); + + throw std::runtime_error("Received unexpected message of type " + json["type"].String()); } void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json) @@ -358,6 +369,7 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co // send active accounts list to new account // and update acount list to everybody else broadcastActiveAccounts(); + sendMessage(connection, prepareActiveGameRooms()); } void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, const JsonNode & json) @@ -420,18 +432,19 @@ void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connectio { std::string gameRoomID = json["gameRoomID"].String(); std::string guestAccountID = json["guestAccountID"].String(); - std::string hostCookie = json["hostCookie"].String(); + std::string accountCookie = json["accountCookie"].String(); - auto clientCookieStatus = database->getGameRoomCookieStatus(gameRoomID, hostCookie, accountCookieLifetime); + // FIXME: find host account ID and validate his cookie + //auto clientCookieStatus = database->getAccountCookieStatus(hostAccountID, accountCookie, accountCookieLifetime); - if(clientCookieStatus != LobbyCookieStatus::INVALID) + //if(clientCookieStatus != LobbyCookieStatus::INVALID) { NetworkConnectionPtr targetAccount = findAccount(guestAccountID); if(targetAccount == nullptr) return; // unknown / disconnected account - sendJoinRoomSuccess(targetAccount, gameRoomID); + sendJoinRoomSuccess(targetAccount, gameRoomID, true); AwaitingProxyState proxy; proxy.accountID = guestAccountID; @@ -441,7 +454,7 @@ void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connectio return; } - networkServer->closeConnection(connection); + //networkServer->closeConnection(connection); } void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json) @@ -467,7 +480,7 @@ void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, c database->insertPlayerIntoGameRoom(accountID, gameRoomID); broadcastActiveGameRooms(); - sendJoinRoomSuccess(connection, gameRoomID); + sendJoinRoomSuccess(connection, gameRoomID, false); } void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json) diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index e70d2e9db..41f0f8596 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -70,13 +70,15 @@ class LobbyServer : public INetworkServerListener void broadcastActiveAccounts(); void broadcastActiveGameRooms(); + JsonNode prepareActiveGameRooms(); + void sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & accountID, const std::string & displayName, const std::string & messageText); void sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie); void sendLoginFailed(const NetworkConnectionPtr & target, const std::string & reason); void sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie, const std::string & displayName); void sendChatHistory(const NetworkConnectionPtr & target, const std::vector &); void sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID); - void sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID); + void sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID, bool proxyMode); void sendInviteReceived(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & gameRoomID); void receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json); diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index a4ad75b21..c044d2d03 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -34,29 +34,39 @@ void GlobalLobbyProcessor::onDisconnected(const std::shared_ptr & connection, const std::vector & message) { - JsonNode json(message.data(), message.size()); + if (connection == controlConnection) + { + JsonNode json(message.data(), message.size()); - if(json["type"].String() == "loginFailed") - return receiveLoginFailed(json); + if(json["type"].String() == "loginFailed") + return receiveLoginFailed(json); - if(json["type"].String() == "loginSuccess") - return receiveLoginSuccess(json); + if(json["type"].String() == "loginSuccess") + return receiveLoginSuccess(json); - if(json["type"].String() == "accountJoinsRoom") - return receiveAccountJoinsRoom(json); + if(json["type"].String() == "accountJoinsRoom") + return receiveAccountJoinsRoom(json); - throw std::runtime_error("Received unexpected message from lobby server: " + json["type"].String()); + throw std::runtime_error("Received unexpected message from lobby server: " + json["type"].String()); + } + else + { + // received game message via proxy connection + owner.onPacketReceived(connection, message); + } } void GlobalLobbyProcessor::receiveLoginFailed(const JsonNode & json) { + logGlobal->info("Lobby: Failed to login into a lobby server!"); + throw std::runtime_error("Failed to login into a lobby server!"); } void GlobalLobbyProcessor::receiveLoginSuccess(const JsonNode & json) { // no-op, wait just for any new commands from lobby - logGlobal->info("Succesfully connected to lobby server"); + logGlobal->info("Lobby: Succesfully connected to lobby server"); owner.startAcceptingIncomingConnections(); } @@ -64,6 +74,7 @@ void GlobalLobbyProcessor::receiveAccountJoinsRoom(const JsonNode & json) { std::string accountID = json["accountID"].String(); + logGlobal->info("Lobby: Account %s will join our room!", accountID); assert(proxyConnections.count(accountID) == 0); proxyConnections[accountID] = nullptr; @@ -87,29 +98,29 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr payloadBuffer(payloadBegin, payloadEnd); - controlConnection->sendPacket(payloadBuffer); + target->sendPacket(payloadBuffer); } diff --git a/server/GlobalLobbyProcessor.h b/server/GlobalLobbyProcessor.h index dd53381dc..84ddb0f5f 100644 --- a/server/GlobalLobbyProcessor.h +++ b/server/GlobalLobbyProcessor.h @@ -21,15 +21,15 @@ class GlobalLobbyProcessor : public INetworkClientListener { CVCMIServer & owner; - std::shared_ptr controlConnection; - std::map> proxyConnections; + NetworkConnectionPtr controlConnection; + std::map proxyConnections; void onDisconnected(const std::shared_ptr & connection) override; void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; void onConnectionEstablished(const std::shared_ptr &) override; - void sendMessage(const JsonNode & data); + void sendMessage(const NetworkConnectionPtr & target, const JsonNode & data); void receiveLoginFailed(const JsonNode & json); void receiveLoginSuccess(const JsonNode & json); From aa2fc4b88a417d785097ee8c243e63e139e7b24d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 1 Feb 2024 20:38:38 +0100 Subject: [PATCH 46/98] adjustable queue size for embedded queue --- client/battle/BattleInterfaceClasses.cpp | 9 +++++++-- client/battle/BattleInterfaceClasses.h | 2 +- config/schemas/settings.json | 6 +++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 739231f83..01683dc58 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -852,9 +852,14 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner) owner(owner) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + + uint32_t queueSize = QUEUE_SIZE_BIG; + if(embedded) { - pos.w = QUEUE_SIZE * 41; + queueSize = std::clamp(static_cast(settings["battle"]["queueSizeEmbeddedSlots"].Float()), 1, 19); + + pos.w = queueSize * 41; pos.h = 49; pos.x += parent->pos.w/2 - pos.w/2; pos.y += 10; @@ -878,7 +883,7 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner) } stateIcons->preload(); - stackBoxes.resize(QUEUE_SIZE); + stackBoxes.resize(queueSize); for (int i = 0; i < stackBoxes.size(); i++) { stackBoxes[i] = std::make_shared(this); diff --git a/client/battle/BattleInterfaceClasses.h b/client/battle/BattleInterfaceClasses.h index cdd7f0f66..1a3d69ce1 100644 --- a/client/battle/BattleInterfaceClasses.h +++ b/client/battle/BattleInterfaceClasses.h @@ -239,7 +239,7 @@ class StackQueue : public CIntObject std::optional getBoundUnitID() const; }; - static const int QUEUE_SIZE = 10; + static const int QUEUE_SIZE_BIG = 10; std::shared_ptr background; std::vector> stackBoxes; BattleInterface & owner; diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 022cf10ab..4663efdfb 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -304,7 +304,7 @@ "type" : "object", "additionalProperties" : false, "default" : {}, - "required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize", "stickyHeroInfoWindows", "enableAutocombatSpells", "endWithAutocombat" ], + "required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize", "stickyHeroInfoWindows", "enableAutocombatSpells", "endWithAutocombat", "queueSizeEmbeddedSlots" ], "properties" : { "speedFactor" : { "type" : "number", @@ -354,6 +354,10 @@ "endWithAutocombat" : { "type": "boolean", "default": false + }, + "queueSizeEmbeddedSlots" : { + "type": "number", + "default": 10 } } }, From 25188aead8d86ca2cf1bd7e85caf069a32dd4102 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 1 Feb 2024 20:50:58 +0100 Subject: [PATCH 47/98] change config name --- client/battle/BattleInterfaceClasses.cpp | 2 +- config/schemas/settings.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 01683dc58..df730dcca 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -857,7 +857,7 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner) if(embedded) { - queueSize = std::clamp(static_cast(settings["battle"]["queueSizeEmbeddedSlots"].Float()), 1, 19); + queueSize = std::clamp(static_cast(settings["battle"]["queueSmallSlots"].Float()), 1, 19); pos.w = queueSize * 41; pos.h = 49; diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 4663efdfb..0a2bb6e2c 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -304,7 +304,7 @@ "type" : "object", "additionalProperties" : false, "default" : {}, - "required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize", "stickyHeroInfoWindows", "enableAutocombatSpells", "endWithAutocombat", "queueSizeEmbeddedSlots" ], + "required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize", "stickyHeroInfoWindows", "enableAutocombatSpells", "endWithAutocombat", "queueSmallSlots" ], "properties" : { "speedFactor" : { "type" : "number", @@ -355,7 +355,7 @@ "type": "boolean", "default": false }, - "queueSizeEmbeddedSlots" : { + "queueSmallSlots" : { "type": "number", "default": 10 } From 5e3f0e631c3ba7645c59836787316f121822a9e3 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 1 Feb 2024 21:17:08 +0100 Subject: [PATCH 48/98] possibility to show small queue outside --- client/battle/BattleInterfaceClasses.cpp | 6 ++++-- config/schemas/settings.json | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index df730dcca..e2cd006c1 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -857,12 +857,14 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner) if(embedded) { - queueSize = std::clamp(static_cast(settings["battle"]["queueSmallSlots"].Float()), 1, 19); + int32_t queueSmallOutsideYOffset = 65; + bool queueSmallOutside = settings["battle"]["queueSmallOutside"].Bool() && (pos.y - queueSmallOutsideYOffset) >= 0; + queueSize = std::clamp(static_cast(settings["battle"]["queueSmallSlots"].Float()), 1, queueSmallOutside ? GH.screenDimensions().x / 41 : 19); pos.w = queueSize * 41; pos.h = 49; pos.x += parent->pos.w/2 - pos.w/2; - pos.y += 10; + pos.y += queueSmallOutside ? -queueSmallOutsideYOffset : 10; icons = GH.renderHandler().loadAnimation(AnimationPath::builtin("CPRSMALL")); stateIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL")); diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 0a2bb6e2c..c5b076a39 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -304,7 +304,7 @@ "type" : "object", "additionalProperties" : false, "default" : {}, - "required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize", "stickyHeroInfoWindows", "enableAutocombatSpells", "endWithAutocombat", "queueSmallSlots" ], + "required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize", "stickyHeroInfoWindows", "enableAutocombatSpells", "endWithAutocombat", "queueSmallSlots", "queueSmallOutside" ], "properties" : { "speedFactor" : { "type" : "number", @@ -358,6 +358,10 @@ "queueSmallSlots" : { "type": "number", "default": 10 + }, + "queueSmallOutside" : { + "type": "boolean", + "default": false } } }, From c9ebf32efd5704d2add6f895bc80269b8153ab32 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Feb 2024 00:12:30 +0200 Subject: [PATCH 49/98] Send error messages if operation fails --- client/globalLobby/GlobalLobbyClient.cpp | 6 +- client/globalLobby/GlobalLobbyClient.h | 2 +- lobby/LobbyDatabase.cpp | 19 ++++++ lobby/LobbyDatabase.h | 1 + lobby/LobbyServer.cpp | 87 +++++++++++++----------- lobby/LobbyServer.h | 2 +- server/GlobalLobbyProcessor.cpp | 6 +- server/GlobalLobbyProcessor.h | 2 +- 8 files changed, 77 insertions(+), 48 deletions(-) diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index a5fc9dc5e..a14bf6803 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -45,8 +45,8 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptrprepare(createTableGameRoomInvites)->execute(); } +void LobbyDatabase::clearOldData() +{ + static const std::string removeActiveAccounts = R"( + UPDATE accounts + SET online = 0 + WHERE online <> 0 + )"; + + static const std::string removeActiveRooms = R"( + UPDATE gameRooms + SET status = 5 + WHERE status <> 5 + )"; + + database->prepare(removeActiveAccounts)->execute(); + database->prepare(removeActiveRooms)->execute(); +} + void LobbyDatabase::prepareStatements() { // INSERT INTO @@ -276,6 +294,7 @@ LobbyDatabase::LobbyDatabase(const boost::filesystem::path & databasePath) { database = SQLiteInstance::open(databasePath, true); createTables(); + clearOldData(); prepareStatements(); } diff --git a/lobby/LobbyDatabase.h b/lobby/LobbyDatabase.h index 52a7ca5ee..43caeae3e 100644 --- a/lobby/LobbyDatabase.h +++ b/lobby/LobbyDatabase.h @@ -54,6 +54,7 @@ class LobbyDatabase void prepareStatements(); void createTables(); + void clearOldData(); public: explicit LobbyDatabase(const boost::filesystem::path & databasePath); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index ed774d85b..63f0c851e 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -90,10 +90,10 @@ void LobbyServer::sendInviteReceived(const NetworkConnectionPtr & target, const sendMessage(target, reply); } -void LobbyServer::sendLoginFailed(const NetworkConnectionPtr & target, const std::string & reason) +void LobbyServer::sendOperationFailed(const NetworkConnectionPtr & target, const std::string & reason) { JsonNode reply; - reply["type"].String() = "loginFailed"; + reply["type"].String() = "operationFailed"; reply["reason"].String() = reason; sendMessage(target, reply); } @@ -161,7 +161,6 @@ JsonNode LobbyServer::prepareActiveGameRooms() jsonEntry["hostAccountID"].String() = gameRoom.hostAccountID; jsonEntry["hostAccountDisplayName"].String() = gameRoom.hostAccountDisplayName; jsonEntry["description"].String() = "TODO: ROOM DESCRIPTION"; -// jsonEntry["status"].String() = gameRoom.roomStatus; jsonEntry["playersCount"].Integer() = gameRoom.playersCount; jsonEntry["playersLimit"].Integer() = gameRoom.playersLimit; reply["gameRooms"].Vector().push_back(jsonEntry); @@ -240,7 +239,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons if(lockedPtr) return lockedPtr->sendPacket(message); - throw std::runtime_error("Received unexpected message for inactive proxy!"); + logGlobal->info("Received unexpected message for inactive proxy!"); } JsonNode json(message.data(), message.size()); @@ -248,56 +247,60 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons // TODO: check for json parsing errors // TODO: validate json based on received message type + std::string messageType = json["type"].String(); + // communication messages from vcmiclient if(activeAccounts.count(connection)) { - if(json["type"].String() == "sendChatMessage") + if(messageType == "sendChatMessage") return receiveSendChatMessage(connection, json); - if(json["type"].String() == "openGameRoom") + if(messageType == "openGameRoom") return receiveOpenGameRoom(connection, json); - if(json["type"].String() == "joinGameRoom") + if(messageType == "joinGameRoom") return receiveJoinGameRoom(connection, json); - if(json["type"].String() == "sendInvite") + if(messageType == "sendInvite") return receiveSendInvite(connection, json); - if(json["type"].String() == "declineInvite") + if(messageType == "declineInvite") return receiveDeclineInvite(connection, json); - throw std::runtime_error("Received unexpected message of type " + json["type"].String()); + logGlobal->info("Received unexpected message of type %s from account %s", messageType, activeAccounts.at(connection).accountID); + return; } // communication messages from vcmiserver if(activeGameRooms.count(connection)) { - if(json["type"].String() == "leaveGameRoom") + if(messageType == "leaveGameRoom") return receiveLeaveGameRoom(connection, json); - throw std::runtime_error("Received unexpected message of type " + json["type"].String()); + logGlobal->info("Received unexpected message of type %s from game room %s", messageType, activeGameRooms.at(connection).roomID); + return; } // unauthorized connections - permit only login or register attempts - if(json["type"].String() == "clientLogin") + if(messageType == "clientLogin") return receiveClientLogin(connection, json); - if(json["type"].String() == "clientRegister") + if(messageType == "clientRegister") return receiveClientRegister(connection, json); - if(json["type"].String() == "serverLogin") + if(messageType == "serverLogin") return receiveServerLogin(connection, json); - if(json["type"].String() == "clientProxyLogin") + if(messageType == "clientProxyLogin") return receiveClientProxyLogin(connection, json); - if(json["type"].String() == "serverProxyLogin") + if(messageType == "serverProxyLogin") return receiveServerProxyLogin(connection, json); // TODO: add logging of suspicious connections. networkServer->closeConnection(connection); - throw std::runtime_error("Received unexpected message of type " + json["type"].String()); + logGlobal->info("Received unexpected message of type %s from not authorised account!", messageType); } void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json) @@ -308,7 +311,7 @@ void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection std::string displayName = database->getAccountDisplayName(accountID); if(messageTextClean.empty()) - return; + return sendOperationFailed(connection, "No printable characters in sent message!"); database->insertChatMessage(accountID, "global", "english", messageText); @@ -322,10 +325,10 @@ void LobbyServer::receiveClientRegister(const NetworkConnectionPtr & connection, std::string language = json["language"].String(); if(isAccountNameValid(displayName)) - return sendLoginFailed(connection, "Illegal account name"); + return sendOperationFailed(connection, "Illegal account name"); if(database->isAccountNameExists(displayName)) - return sendLoginFailed(connection, "Account name already in use"); + return sendOperationFailed(connection, "Account name already in use"); std::string accountCookie = boost::uuids::to_string(boost::uuids::random_generator()()); std::string accountID = boost::uuids::to_string(boost::uuids::random_generator()()); @@ -344,12 +347,12 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co std::string version = json["version"].String(); if(!database->isAccountIDExists(accountID)) - return sendLoginFailed(connection, "Account not found"); + return sendOperationFailed(connection, "Account not found"); auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime); if(clientCookieStatus == LobbyCookieStatus::INVALID) - return sendLoginFailed(connection, "Authentification failure"); + return sendOperationFailed(connection, "Authentification failure"); // prolong existing cookie database->updateAccessCookie(accountID, accountCookie); @@ -383,16 +386,15 @@ void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, co if(clientCookieStatus == LobbyCookieStatus::INVALID) { - sendLoginFailed(connection, "Invalid credentials"); + sendOperationFailed(connection, "Invalid credentials"); } else { database->insertGameRoom(gameRoomID, accountID); activeGameRooms[connection].roomID = gameRoomID; sendLoginSuccess(connection, accountCookie, {}); + broadcastActiveGameRooms(); } - - broadcastActiveGameRooms(); } void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json) @@ -425,6 +427,7 @@ void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connectio } } + sendOperationFailed(connection, "Invalid credentials"); networkServer->closeConnection(connection); } @@ -442,7 +445,10 @@ void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connectio NetworkConnectionPtr targetAccount = findAccount(guestAccountID); if(targetAccount == nullptr) + { + sendOperationFailed(connection, "Invalid credentials"); return; // unknown / disconnected account + } sendJoinRoomSuccess(targetAccount, gameRoomID, true); @@ -463,13 +469,16 @@ void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, c std::string accountID = activeAccounts[connection].accountID; if(database->isPlayerInGameRoom(accountID)) - return; // only 1 room per player allowed + return sendOperationFailed(connection, "Player already in the room!"); std::string gameRoomID = database->getIdleGameRoom(hostAccountID); if(gameRoomID.empty()) - return; + return sendOperationFailed(connection, "Failed to find idle server to join!"); std::string roomType = json["roomType"].String(); + if(roomType != "public" && roomType != "private") + return sendOperationFailed(connection, "Invalid room type!"); + if(roomType == "public") database->setGameRoomStatus(gameRoomID, LobbyRoomState::PUBLIC); if(roomType == "private") @@ -489,26 +498,26 @@ void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, c std::string accountID = activeAccounts[connection].accountID; if(database->isPlayerInGameRoom(accountID)) - return; // only 1 room per player allowed + return sendOperationFailed(connection, "Player already in the room!"); NetworkConnectionPtr targetRoom = findGameRoom(gameRoomID); if(targetRoom == nullptr) - return; // unknown / disconnected room + return sendOperationFailed(connection, "Failed to find game room to join!"); auto roomStatus = database->getGameRoomStatus(gameRoomID); if(roomStatus != LobbyRoomState::PRIVATE && roomStatus != LobbyRoomState::PUBLIC) - return; + return sendOperationFailed(connection, "Room does not accepts new players!"); if(roomStatus == LobbyRoomState::PRIVATE) { if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED) - return; + return sendOperationFailed(connection, "You are not permitted to join private room without invite!"); } if(database->getGameRoomFreeSlots(gameRoomID) == 0) - return; + return sendOperationFailed(connection, "Room is already full!"); database->insertPlayerIntoGameRoom(accountID, gameRoomID); sendAccountJoinsRoom(targetRoom, accountID); @@ -523,7 +532,7 @@ void LobbyServer::receiveLeaveGameRoom(const NetworkConnectionPtr & connection, std::string senderName = activeAccounts[connection].accountID; if(!database->isPlayerInGameRoom(senderName, gameRoomID)) - return; + return sendOperationFailed(connection, "You are not in the room!"); broadcastActiveGameRooms(); } @@ -537,16 +546,16 @@ void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, con auto targetAccount = findAccount(accountID); if(!targetAccount) - return; // target player does not exists or offline + return sendOperationFailed(connection, "Invalid account to invite!"); if(!database->isPlayerInGameRoom(senderName)) - return; // current player is not in room + return sendOperationFailed(connection, "You are not in the room!"); if(database->isPlayerInGameRoom(accountID)) - return; // target player is busy + return sendOperationFailed(connection, "This player is already in a room!"); if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::NOT_INVITED) - return; // already has invite + return sendOperationFailed(connection, "This player is already invited!"); database->insertGameRoomInvite(accountID, gameRoomID); sendInviteReceived(targetAccount, senderName, gameRoomID); @@ -558,7 +567,7 @@ void LobbyServer::receiveDeclineInvite(const NetworkConnectionPtr & connection, std::string gameRoomID = json["gameRoomID"].String(); if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED) - return; // already has invite + return sendOperationFailed(connection, "No active invite found!"); database->deleteGameRoomInvite(accountID, gameRoomID); } diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index 41f0f8596..7e68c0e48 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -74,7 +74,7 @@ class LobbyServer : public INetworkServerListener void sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & accountID, const std::string & displayName, const std::string & messageText); void sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie); - void sendLoginFailed(const NetworkConnectionPtr & target, const std::string & reason); + void sendOperationFailed(const NetworkConnectionPtr & target, const std::string & reason); void sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie, const std::string & displayName); void sendChatHistory(const NetworkConnectionPtr & target, const std::vector &); void sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID); diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index c044d2d03..b389d5621 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -38,8 +38,8 @@ void GlobalLobbyProcessor::onPacketReceived(const std::shared_ptrinfo("Lobby: Failed to login into a lobby server!"); diff --git a/server/GlobalLobbyProcessor.h b/server/GlobalLobbyProcessor.h index 84ddb0f5f..15044c2dd 100644 --- a/server/GlobalLobbyProcessor.h +++ b/server/GlobalLobbyProcessor.h @@ -31,7 +31,7 @@ class GlobalLobbyProcessor : public INetworkClientListener void sendMessage(const NetworkConnectionPtr & target, const JsonNode & data); - void receiveLoginFailed(const JsonNode & json); + void receiveOperationFailed(const JsonNode & json); void receiveLoginSuccess(const JsonNode & json); void receiveAccountJoinsRoom(const JsonNode & json); From ad547fcae0aa983c5d1d892cb7f9183e654947d6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Feb 2024 00:21:52 +0200 Subject: [PATCH 50/98] Added basic logging --- lobby/EntryPoint.cpp | 9 +++++++++ lobby/LobbyServer.cpp | 14 +++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lobby/EntryPoint.cpp b/lobby/EntryPoint.cpp index de4d3049e..48a6cd783 100644 --- a/lobby/EntryPoint.cpp +++ b/lobby/EntryPoint.cpp @@ -11,15 +11,24 @@ #include "LobbyServer.h" +#include "../lib/logging/CBasicLogConfigurator.h" #include "../lib/VCMIDirs.h" static const int LISTENING_PORT = 30303; int main(int argc, const char * argv[]) { +#ifndef VCMI_IOS + console = new CConsoleHandler(); +#endif + CBasicLogConfigurator logConfig(VCMIDirs::get().userLogsPath() / "VCMI_Lobby_log.txt", console); + logConfig.configureDefault(); + auto databasePath = VCMIDirs::get().userDataPath() / "vcmiLobby.db"; + logGlobal->info("Opening database %s", databasePath.string()); LobbyServer server(databasePath); + logGlobal->info("Starting server on port %d", LISTENING_PORT); server.start(LISTENING_PORT); server.run(); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 63f0c851e..5d432ae9f 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -252,6 +252,9 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons // communication messages from vcmiclient if(activeAccounts.count(connection)) { + std::string accountName = activeAccounts.at(connection).accountID; + logGlobal->info("%s: Received message of type %s", accountName, messageType); + if(messageType == "sendChatMessage") return receiveSendChatMessage(connection, json); @@ -267,20 +270,25 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons if(messageType == "declineInvite") return receiveDeclineInvite(connection, json); - logGlobal->info("Received unexpected message of type %s from account %s", messageType, activeAccounts.at(connection).accountID); + logGlobal->warn("%s: Unknown message type: %s", accountName, messageType); return; } // communication messages from vcmiserver if(activeGameRooms.count(connection)) { + std::string roomName = activeGameRooms.at(connection).roomID; + logGlobal->info("%s: Received message of type %s", roomName, messageType); + if(messageType == "leaveGameRoom") return receiveLeaveGameRoom(connection, json); - logGlobal->info("Received unexpected message of type %s from game room %s", messageType, activeGameRooms.at(connection).roomID); + logGlobal->warn("%s: Unknown message type: %s", roomName, messageType); return; } + logGlobal->info("(unauthorised): Received message of type %s", messageType); + // unauthorized connections - permit only login or register attempts if(messageType == "clientLogin") return receiveClientLogin(connection, json); @@ -300,7 +308,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons // TODO: add logging of suspicious connections. networkServer->closeConnection(connection); - logGlobal->info("Received unexpected message of type %s from not authorised account!", messageType); + logGlobal->info("(unauthorised): Unknown message type %s", messageType); } void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json) From c12558bf8a1f6ec5fe2cfe590dcd7bda59e6e61d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Feb 2024 00:29:15 +0200 Subject: [PATCH 51/98] Simplified code --- client/CServerHandler.cpp | 10 +++++++++- lobby/LobbyDefines.h | 1 - lobby/LobbyServer.cpp | 31 ++++++++++++++----------------- lobby/LobbyServer.h | 23 ++++++----------------- lobby/SQLiteConnection.h | 2 +- 5 files changed, 30 insertions(+), 37 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 9490a97d9..a57854f85 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -129,7 +129,15 @@ public: CServerHandler::~CServerHandler() { networkHandler->stop(); - threadNetwork->join(); + try + { + threadNetwork->join(); + } + catch (const std::runtime_error & e) + { + logGlobal->error("Failed to shut down network thread! Reason: %s", e.what()); + assert(false); + } } CServerHandler::CServerHandler() diff --git a/lobby/LobbyDefines.h b/lobby/LobbyDefines.h index 789c79028..710d9277d 100644 --- a/lobby/LobbyDefines.h +++ b/lobby/LobbyDefines.h @@ -13,7 +13,6 @@ struct LobbyAccount { std::string accountID; std::string displayName; - //std::string status; }; struct LobbyGameRoom diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 5d432ae9f..47ef3d83d 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -43,7 +43,7 @@ std::string LobbyServer::sanitizeChatMessage(const std::string & inputString) co NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID) const { for(const auto & account : activeAccounts) - if(account.second.accountID == accountID) + if(account.second == accountID) return account.first; return nullptr; @@ -52,7 +52,7 @@ NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID) con NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) const { for(const auto & account : activeGameRooms) - if(account.second.roomID == gameRoomID) + if(account.second == gameRoomID) return account.first; return nullptr; @@ -215,10 +215,10 @@ void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection) void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection) { if (activeAccounts.count(connection)) - database->setAccountOnline(activeAccounts.at(connection).accountID, false); + database->setAccountOnline(activeAccounts.at(connection), false); if (activeGameRooms.count(connection)) - database->setGameRoomStatus(activeGameRooms.at(connection).roomID, LobbyRoomState::CLOSED); + database->setGameRoomStatus(activeGameRooms.at(connection), LobbyRoomState::CLOSED); // NOTE: lost connection can be in only one of these lists (or in none of them) // calling on all possible containers since calling std::map::erase() with non-existing key is legal @@ -252,7 +252,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons // communication messages from vcmiclient if(activeAccounts.count(connection)) { - std::string accountName = activeAccounts.at(connection).accountID; + std::string accountName = activeAccounts.at(connection); logGlobal->info("%s: Received message of type %s", accountName, messageType); if(messageType == "sendChatMessage") @@ -277,7 +277,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons // communication messages from vcmiserver if(activeGameRooms.count(connection)) { - std::string roomName = activeGameRooms.at(connection).roomID; + std::string roomName = activeGameRooms.at(connection); logGlobal->info("%s: Received message of type %s", roomName, messageType); if(messageType == "leaveGameRoom") @@ -313,7 +313,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json) { - std::string accountID = activeAccounts[connection].accountID; + std::string accountID = activeAccounts[connection]; std::string messageText = json["messageText"].String(); std::string messageTextClean = sanitizeChatMessage(messageText); std::string displayName = database->getAccountDisplayName(accountID); @@ -369,10 +369,7 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co std::string displayName = database->getAccountDisplayName(accountID); - activeAccounts[connection].accountID = accountID; - activeAccounts[connection].displayName = displayName; - activeAccounts[connection].version = version; - activeAccounts[connection].language = language; + activeAccounts[connection] = accountID; sendLoginSuccess(connection, accountCookie, displayName); sendChatHistory(connection, database->getRecentMessageHistory()); @@ -399,7 +396,7 @@ void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, co else { database->insertGameRoom(gameRoomID, accountID); - activeGameRooms[connection].roomID = gameRoomID; + activeGameRooms[connection] = gameRoomID; sendLoginSuccess(connection, accountCookie, {}); broadcastActiveGameRooms(); } @@ -474,7 +471,7 @@ void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connectio void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json) { std::string hostAccountID = json["hostAccountID"].String(); - std::string accountID = activeAccounts[connection].accountID; + std::string accountID = activeAccounts[connection]; if(database->isPlayerInGameRoom(accountID)) return sendOperationFailed(connection, "Player already in the room!"); @@ -503,7 +500,7 @@ void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, c void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json) { std::string gameRoomID = json["gameRoomID"].String(); - std::string accountID = activeAccounts[connection].accountID; + std::string accountID = activeAccounts[connection]; if(database->isPlayerInGameRoom(accountID)) return sendOperationFailed(connection, "Player already in the room!"); @@ -537,7 +534,7 @@ void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, c void LobbyServer::receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json) { std::string gameRoomID = json["gameRoomID"].String(); - std::string senderName = activeAccounts[connection].accountID; + std::string senderName = activeAccounts[connection]; if(!database->isPlayerInGameRoom(senderName, gameRoomID)) return sendOperationFailed(connection, "You are not in the room!"); @@ -547,7 +544,7 @@ void LobbyServer::receiveLeaveGameRoom(const NetworkConnectionPtr & connection, void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, const JsonNode & json) { - std::string senderName = activeAccounts[connection].accountID; + std::string senderName = activeAccounts[connection]; std::string accountID = json["accountID"].String(); std::string gameRoomID = database->getAccountGameRoom(senderName); @@ -571,7 +568,7 @@ void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, con void LobbyServer::receiveDeclineInvite(const NetworkConnectionPtr & connection, const JsonNode & json) { - std::string accountID = activeAccounts[connection].accountID; + std::string accountID = activeAccounts[connection]; std::string gameRoomID = json["gameRoomID"].String(); if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED) diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index 7e68c0e48..4cc211545 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -18,38 +18,27 @@ VCMI_LIB_NAMESPACE_END class LobbyDatabase; -class LobbyServer : public INetworkServerListener +class LobbyServer final : public INetworkServerListener { - struct AccountState - { - std::string accountID; - std::string displayName; - std::string version; - std::string language; - }; - struct GameRoomState - { - std::string roomID; - }; struct AwaitingProxyState { std::string accountID; std::string roomID; - std::weak_ptr accountConnection; - std::weak_ptr roomConnection; + NetworkConnectionWeakPtr accountConnection; + NetworkConnectionWeakPtr roomConnection; }; /// list of connected proxies. All messages received from (key) will be redirected to (value) connection - std::map> activeProxies; + std::map activeProxies; /// list of half-established proxies from server that are still waiting for client to connect std::vector awaitingProxies; /// list of logged in accounts (vcmiclient's) - std::map activeAccounts; + std::map activeAccounts; /// list of currently logged in game rooms (vcmiserver's) - std::map activeGameRooms; + std::map activeGameRooms; std::unique_ptr database; std::unique_ptr networkHandler; diff --git a/lobby/SQLiteConnection.h b/lobby/SQLiteConnection.h index f501c5c83..834caec88 100644 --- a/lobby/SQLiteConnection.h +++ b/lobby/SQLiteConnection.h @@ -109,7 +109,7 @@ public: SQLiteStatementPtr prepare(const std::string & statement); private: - SQLiteInstance(sqlite3 * connection); + explicit SQLiteInstance(sqlite3 * connection); sqlite3 * m_connection; }; From 29c0989849c965a1c5d005bd0cdb42625974201e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Feb 2024 01:27:19 +0200 Subject: [PATCH 52/98] Use std::byte to manage network data --- client/CServerHandler.cpp | 4 +-- client/CServerHandler.h | 4 +-- client/globalLobby/GlobalLobbyClient.cpp | 24 +++-------------- client/globalLobby/GlobalLobbyClient.h | 4 +-- lib/JsonNode.cpp | 13 +++++++++ lib/JsonNode.h | 2 ++ lib/network/NetworkConnection.cpp | 34 +++++++++--------------- lib/network/NetworkConnection.h | 2 +- lib/network/NetworkInterface.h | 10 +++---- lib/network/NetworkServer.cpp | 10 +++---- lib/network/NetworkServer.h | 6 ++--- lib/serializer/Connection.cpp | 12 ++++----- lib/serializer/Connection.h | 2 +- lobby/LobbyServer.cpp | 15 +++-------- lobby/LobbyServer.h | 4 +-- server/CVCMIServer.cpp | 4 +-- server/CVCMIServer.h | 4 +-- server/GlobalLobbyProcessor.cpp | 14 +++------- server/GlobalLobbyProcessor.h | 4 +-- 19 files changed, 72 insertions(+), 100 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index a57854f85..0516cc721 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -875,7 +875,7 @@ public: } }; -void CServerHandler::onPacketReceived(const std::shared_ptr &, const std::vector & message) +void CServerHandler::onPacketReceived(const std::shared_ptr &, const std::vector & message) { CPack * pack = c->retrievePack(message); if(state == EClientState::DISCONNECTING) @@ -891,7 +891,7 @@ void CServerHandler::onPacketReceived(const std::shared_ptr } } -void CServerHandler::onDisconnected(const std::shared_ptr & connection) +void CServerHandler::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { assert(networkConnection == connection); networkConnection.reset(); diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 923a319b6..657747e24 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -114,10 +114,10 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor void onServerFinished(); void sendLobbyPack(const CPackForLobby & pack) const override; - void onPacketReceived(const NetworkConnectionPtr &, const std::vector & message) override; + void onPacketReceived(const NetworkConnectionPtr &, const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; void onConnectionEstablished(const NetworkConnectionPtr &) override; - void onDisconnected(const NetworkConnectionPtr &) override; + void onDisconnected(const NetworkConnectionPtr &, const std::string & errorMessage) override; void onTimer() override; void applyPackOnLobbyScreen(CPackForLobby & pack); diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index a14bf6803..dd4540ab0 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -36,7 +36,7 @@ static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0) return TextOperations::getFormattedTimeLocal(std::chrono::system_clock::to_time_t(timeNowChrono)); } -void GlobalLobbyClient::onPacketReceived(const std::shared_ptr &, const std::vector & message) +void GlobalLobbyClient::onPacketReceived(const std::shared_ptr &, const std::vector & message) { boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); @@ -247,7 +247,7 @@ void GlobalLobbyClient::onConnectionFailed(const std::string & errorMessage) loginWindowPtr->onConnectionFailed(errorMessage); } -void GlobalLobbyClient::onDisconnected(const std::shared_ptr & connection) +void GlobalLobbyClient::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { assert(connection == networkConnection); networkConnection.reset(); @@ -258,15 +258,7 @@ void GlobalLobbyClient::onDisconnected(const std::shared_ptr void GlobalLobbyClient::sendMessage(const JsonNode & data) { - std::string payloadString = data.toJson(true); - - // FIXME: find better approach - uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); - uint8_t * payloadEnd = payloadBegin + payloadString.size(); - - std::vector payloadBuffer(payloadBegin, payloadEnd); - - networkConnection->sendPacket(payloadBuffer); + networkConnection->sendPacket(data.toBytes(true)); } void GlobalLobbyClient::sendOpenPublicRoom() @@ -355,13 +347,5 @@ void GlobalLobbyClient::sendProxyConnectionLogin(const NetworkConnectionPtr & ne toSend["accountCookie"] = settings["lobby"]["accountCookie"]; toSend["gameRoomID"] = settings["lobby"]["roomID"]; - std::string payloadString = toSend.toJson(true); - - // FIXME: find better approach - uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); - uint8_t * payloadEnd = payloadBegin + payloadString.size(); - - std::vector payloadBuffer(payloadBegin, payloadEnd); - - netConnection->sendPacket(payloadBuffer); + netConnection->sendPacket(toSend.toBytes(true)); } diff --git a/client/globalLobby/GlobalLobbyClient.h b/client/globalLobby/GlobalLobbyClient.h index 15c0f3cf8..5d8abb4e0 100644 --- a/client/globalLobby/GlobalLobbyClient.h +++ b/client/globalLobby/GlobalLobbyClient.h @@ -30,10 +30,10 @@ class GlobalLobbyClient final : public INetworkClientListener, boost::noncopyabl std::weak_ptr lobbyWindow; std::shared_ptr lobbyWindowLock; // helper strong reference to prevent window destruction on closing - void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; + void onPacketReceived(const std::shared_ptr &, const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; void onConnectionEstablished(const std::shared_ptr &) override; - void onDisconnected(const std::shared_ptr &) override; + void onDisconnected(const std::shared_ptr &, const std::string & errorMessage) override; void sendClientRegister(); void sendClientLogin(); diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index f168b97d1..4124bd818 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -76,6 +76,10 @@ JsonNode::JsonNode(const uint8_t *data, size_t datasize) :JsonNode(reinterpret_cast(data), datasize) {} +JsonNode::JsonNode(const std::byte *data, size_t datasize) + :JsonNode(reinterpret_cast(data), datasize) +{} + JsonNode::JsonNode(const char *data, size_t datasize) { JsonParser parser(data, datasize); @@ -421,6 +425,15 @@ JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) return ::resolvePointer(*this, jsonPointer); } +std::vector JsonNode::toBytes(bool compact) const +{ + std::string jsonString = toJson(compact); + auto dataBegin = reinterpret_cast(jsonString.data()); + auto dataEnd = dataBegin + jsonString.size(); + std::vector result(dataBegin, dataEnd); + return result; +} + std::string JsonNode::toJson(bool compact) const { std::ostringstream out; diff --git a/lib/JsonNode.h b/lib/JsonNode.h index 3dc47288c..bd9de7886 100644 --- a/lib/JsonNode.h +++ b/lib/JsonNode.h @@ -52,6 +52,7 @@ public: //Create tree from Json-formatted input explicit JsonNode(const char * data, size_t datasize); explicit JsonNode(const uint8_t * data, size_t datasize); + explicit JsonNode(const std::byte * data, size_t datasize); //Create tree from JSON file explicit JsonNode(const JsonPath & fileURI); explicit JsonNode(const std::string & modName, const JsonPath & fileURI); @@ -117,6 +118,7 @@ public: const JsonNode & operator[](size_t child) const; std::string toJson(bool compact = false) const; + std::vector toBytes(bool compact = false) const; template void serialize(Handler &h) { diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index 3ab8279d8..f8a0ae9f1 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -33,7 +33,7 @@ void NetworkConnection::onHeaderReceived(const boost::system::error_code & ec) { if (ec) { - listener.onDisconnected(shared_from_this()); + listener.onDisconnected(shared_from_this(), ec.message()); return; } @@ -50,14 +50,11 @@ uint32_t NetworkConnection::readPacketSize() if (readBuffer.size() < messageHeaderSize) throw std::runtime_error("Failed to read header!"); - std::istream istream(&readBuffer); - uint32_t messageSize; - istream.read(reinterpret_cast(&messageSize), messageHeaderSize); + readBuffer.sgetn(reinterpret_cast(&messageSize), sizeof(messageSize)); + if (messageSize > messageMaxSize) - { throw std::runtime_error("Invalid packet size!"); - } return messageSize; } @@ -66,7 +63,7 @@ void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, u { if (ec) { - listener.onDisconnected(shared_from_this()); + listener.onDisconnected(shared_from_this(), ec.message()); return; } @@ -75,28 +72,21 @@ void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, u throw std::runtime_error("Failed to read header!"); } - std::vector message; - - message.resize(expectedPacketSize); - std::istream istream(&readBuffer); - istream.read(reinterpret_cast(message.data()), expectedPacketSize); - + std::vector message(expectedPacketSize); + readBuffer.sgetn(reinterpret_cast(message.data()), expectedPacketSize); listener.onPacketReceived(shared_from_this(), message); start(); } -void NetworkConnection::sendPacket(const std::vector & message) +void NetworkConnection::sendPacket(const std::vector & message) { - NetworkBuffer writeBuffer; - - std::ostream ostream(&writeBuffer); - uint32_t messageSize = message.size(); - ostream.write(reinterpret_cast(&messageSize), messageHeaderSize); - ostream.write(reinterpret_cast(message.data()), message.size()); - boost::system::error_code ec; - boost::asio::write(*socket, writeBuffer, ec ); + + std::array messageSize{static_cast(message.size())}; + + boost::asio::write(*socket, boost::asio::buffer(messageSize), ec ); + boost::asio::write(*socket, boost::asio::buffer(message), ec ); // FIXME: handle error? } diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h index afdc7d967..76b032bf2 100644 --- a/lib/network/NetworkConnection.h +++ b/lib/network/NetworkConnection.h @@ -31,7 +31,7 @@ public: NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr & socket); void start(); - void sendPacket(const std::vector & message) override; + void sendPacket(const std::vector & message) override; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkInterface.h b/lib/network/NetworkInterface.h index 3fb98006f..ba1752d56 100644 --- a/lib/network/NetworkInterface.h +++ b/lib/network/NetworkInterface.h @@ -16,7 +16,7 @@ class DLL_LINKAGE INetworkConnection : boost::noncopyable { public: virtual ~INetworkConnection() = default; - virtual void sendPacket(const std::vector & message) = 0; + virtual void sendPacket(const std::vector & message) = 0; }; using NetworkConnectionPtr = std::shared_ptr; @@ -29,7 +29,7 @@ public: virtual ~INetworkClient() = default; virtual bool isConnected() const = 0; - virtual void sendPacket(const std::vector & message) = 0; + virtual void sendPacket(const std::vector & message) = 0; }; /// Base class for incoming connections support @@ -38,7 +38,7 @@ class DLL_LINKAGE INetworkServer : boost::noncopyable public: virtual ~INetworkServer() = default; - virtual void sendPacket(const std::shared_ptr &, const std::vector & message) = 0; + virtual void sendPacket(const std::shared_ptr &, const std::vector & message) = 0; virtual void closeConnection(const std::shared_ptr &) = 0; virtual void start(uint16_t port) = 0; }; @@ -47,8 +47,8 @@ public: class DLL_LINKAGE INetworkConnectionListener { public: - virtual void onDisconnected(const std::shared_ptr & connection) = 0; - virtual void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) = 0; + virtual void onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) = 0; + virtual void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) = 0; virtual ~INetworkConnectionListener() = default; }; diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp index 316d78e09..676764c3e 100644 --- a/lib/network/NetworkServer.cpp +++ b/lib/network/NetworkServer.cpp @@ -46,7 +46,7 @@ void NetworkServer::connectionAccepted(std::shared_ptr upcomingCo startAsyncAccept(); } -void NetworkServer::sendPacket(const std::shared_ptr & connection, const std::vector & message) +void NetworkServer::sendPacket(const std::shared_ptr & connection, const std::vector & message) { connection->sendPacket(message); } @@ -58,18 +58,18 @@ void NetworkServer::closeConnection(const std::shared_ptr & connections.erase(connection); } -void NetworkServer::onDisconnected(const std::shared_ptr & connection) +void NetworkServer::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { - logNetwork->info("Connection lost!"); + logNetwork->info("Connection lost! Reason: %s", errorMessage); assert(connections.count(connection)); if (connections.count(connection)) // how? Connection was explicitly closed before? { connections.erase(connection); - listener.onDisconnected(connection); + listener.onDisconnected(connection, errorMessage); } } -void NetworkServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) +void NetworkServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) { listener.onPacketReceived(connection, message); } diff --git a/lib/network/NetworkServer.h b/lib/network/NetworkServer.h index 4c1c57aa2..805adfba5 100644 --- a/lib/network/NetworkServer.h +++ b/lib/network/NetworkServer.h @@ -24,12 +24,12 @@ class NetworkServer : public INetworkConnectionListener, public INetworkServer void connectionAccepted(std::shared_ptr, const boost::system::error_code & ec); void startAsyncAccept(); - void onDisconnected(const std::shared_ptr & connection) override; - void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; + void onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) override; + void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; public: NetworkServer(INetworkServerListener & listener, const std::shared_ptr & context); - void sendPacket(const std::shared_ptr &, const std::vector & message) override; + void sendPacket(const std::shared_ptr &, const std::vector & message) override; void closeConnection(const std::shared_ptr &) override; void start(uint16_t port) override; diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index 6710a084d..33a25363e 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -22,7 +22,7 @@ VCMI_LIB_NAMESPACE_BEGIN class DLL_LINKAGE ConnectionPackWriter final : public IBinaryWriter { public: - std::vector buffer; + std::vector buffer; int write(const void * data, unsigned size) final; }; @@ -30,7 +30,7 @@ public: class DLL_LINKAGE ConnectionPackReader final : public IBinaryReader { public: - const std::vector * buffer; + const std::vector * buffer; size_t position; int read(void * data, unsigned size) final; @@ -38,8 +38,8 @@ public: int ConnectionPackWriter::write(const void * data, unsigned size) { - const uint8_t * begin_ptr = static_cast(data); - const uint8_t * end_ptr = begin_ptr + size; + const std::byte * begin_ptr = static_cast(data); + const std::byte * end_ptr = begin_ptr + size; buffer.insert(buffer.end(), begin_ptr, end_ptr); return size; } @@ -49,7 +49,7 @@ int ConnectionPackReader::read(void * data, unsigned size) if (position + size > buffer->size()) throw std::runtime_error("End of file reached when reading received network pack!"); - uint8_t * begin_ptr = static_cast(data); + std::byte * begin_ptr = static_cast(data); std::copy_n(buffer->begin() + position, size, begin_ptr); position += size; @@ -88,7 +88,7 @@ void CConnection::sendPack(const CPack * pack) packWriter->buffer.clear(); } -CPack * CConnection::retrievePack(const std::vector & data) +CPack * CConnection::retrievePack(const std::vector & data) { CPack * result; diff --git a/lib/serializer/Connection.h b/lib/serializer/Connection.h index 4de171a82..067ab415d 100644 --- a/lib/serializer/Connection.h +++ b/lib/serializer/Connection.h @@ -50,7 +50,7 @@ public: ~CConnection(); void sendPack(const CPack * pack); - CPack * retrievePack(const std::vector & data); + CPack * retrievePack(const std::vector & data); void enterLobbyConnectionMode(); void setCallback(IGameCallback * cb); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 47ef3d83d..5cf741e7e 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -60,16 +60,7 @@ NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) c void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json) { - //NOTE: copy-paste from LobbyClient::sendMessage - std::string payloadString = json.toJson(true); - - // TODO: find better approach - const uint8_t * payloadBegin = reinterpret_cast(payloadString.data()); - const uint8_t * payloadEnd = payloadBegin + payloadString.size(); - - std::vector payloadBuffer(payloadBegin, payloadEnd); - - networkServer->sendPacket(target, payloadBuffer); + networkServer->sendPacket(target, json.toBytes(true)); } void LobbyServer::sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie) @@ -212,7 +203,7 @@ void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection) // no-op - waiting for incoming data } -void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection) +void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection, const std::string & errorMessage) { if (activeAccounts.count(connection)) database->setAccountOnline(activeAccounts.at(connection), false); @@ -230,7 +221,7 @@ void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection) broadcastActiveGameRooms(); } -void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, const std::vector & message) +void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, const std::vector & message) { // proxy connection - no processing, only redirect if(activeProxies.count(connection)) diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index 4cc211545..6a3ebc5fa 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -51,8 +51,8 @@ class LobbyServer final : public INetworkServerListener NetworkConnectionPtr findGameRoom(const std::string & gameRoomID) const; void onNewConnection(const NetworkConnectionPtr & connection) override; - void onDisconnected(const NetworkConnectionPtr & connection) override; - void onPacketReceived(const NetworkConnectionPtr & connection, const std::vector & message) override; + void onDisconnected(const NetworkConnectionPtr & connection, const std::string & errorMessage) override; + void onPacketReceived(const NetworkConnectionPtr & connection, const std::vector & message) override; void sendMessage(const NetworkConnectionPtr & target, const JsonNode & json); diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index bc01e2619..6fe6ee669 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -164,7 +164,7 @@ void CVCMIServer::onNewConnection(const std::shared_ptr & co } } -void CVCMIServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) +void CVCMIServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) { std::shared_ptr c = findConnection(connection); auto pack = c->retrievePack(message); @@ -325,7 +325,7 @@ void CVCMIServer::startGameImmediately() onTimer(); } -void CVCMIServer::onDisconnected(const std::shared_ptr & connection) +void CVCMIServer::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { logNetwork->error("Network error receiving a pack. Connection has been closed"); diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 2b47ab480..bdee4e164 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -77,8 +77,8 @@ private: public: // INetworkListener impl - void onDisconnected(const std::shared_ptr & connection) override; - void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; + void onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) override; + void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; void onNewConnection(const std::shared_ptr &) override; void onTimer() override; diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index b389d5621..23a5037a7 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -27,12 +27,12 @@ void GlobalLobbyProcessor::establishNewConnection() owner.networkHandler->connectToRemote(*this, hostname, port); } -void GlobalLobbyProcessor::onDisconnected(const std::shared_ptr & connection) +void GlobalLobbyProcessor::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { throw std::runtime_error("Lost connection to a lobby server!"); } -void GlobalLobbyProcessor::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) +void GlobalLobbyProcessor::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) { if (connection == controlConnection) { @@ -122,13 +122,5 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr(payloadString.data()); - uint8_t * payloadEnd = payloadBegin + payloadString.size(); - - std::vector payloadBuffer(payloadBegin, payloadEnd); - - target->sendPacket(payloadBuffer); + target->sendPacket(data.toBytes(true)); } diff --git a/server/GlobalLobbyProcessor.h b/server/GlobalLobbyProcessor.h index 15044c2dd..87cb15bbb 100644 --- a/server/GlobalLobbyProcessor.h +++ b/server/GlobalLobbyProcessor.h @@ -24,8 +24,8 @@ class GlobalLobbyProcessor : public INetworkClientListener NetworkConnectionPtr controlConnection; std::map proxyConnections; - void onDisconnected(const std::shared_ptr & connection) override; - void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; + void onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) override; + void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; void onConnectionFailed(const std::string & errorMessage) override; void onConnectionEstablished(const std::shared_ptr &) override; From caa22ad9d8191e8eefad21344d7b43e551804a40 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Fri, 2 Feb 2024 08:52:59 +0100 Subject: [PATCH 53/98] Don't call recreateWindowAndScreenBuffers() in ScreenHandler::onScreenResize() to fix crash due to a huge texture being created --- client/renderSDL/ScreenHandler.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/client/renderSDL/ScreenHandler.cpp b/client/renderSDL/ScreenHandler.cpp index 265c3b021..d3532a2d1 100644 --- a/client/renderSDL/ScreenHandler.cpp +++ b/client/renderSDL/ScreenHandler.cpp @@ -397,7 +397,6 @@ SDL_Window * ScreenHandler::createWindow() void ScreenHandler::onScreenResize() { - recreateWindowAndScreenBuffers(); } void ScreenHandler::validateSettings() From 9f7c986621a4174e4c097a8e9e50b685a8bf3686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 2 Feb 2024 10:37:08 +0100 Subject: [PATCH 54/98] First version that compiles successfully --- lib/rmg/PenroseTiling.cpp | 69 +++++++++++++++++++++++++++------------ lib/rmg/PenroseTiling.h | 19 +++++++++-- 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/lib/rmg/PenroseTiling.cpp b/lib/rmg/PenroseTiling.cpp index 2af1d7b71..70d6696ee 100644 --- a/lib/rmg/PenroseTiling.cpp +++ b/lib/rmg/PenroseTiling.cpp @@ -5,7 +5,6 @@ // Adapted from https://github.com/mpizzzle/penrose -// FIXME: Find library for geometry representation //https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/geometry/reference/adapted/boost_polygon/point_data.html //#include @@ -15,30 +14,59 @@ //#include #include "StdInc.h" +#include "PenroseTiling.h" -#include -#include -#include -#include - -#include -//https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/geometry/reference/strategies/strategy_transform_rotate_transformer.html //#include "shader.hpp" //#include "png_writer.hpp" -#include "PenroseTiling.h" - VCMI_LIB_NAMESPACE_BEGIN //static const std::string file_name = "penrose.png"; +Point2D Point2D::operator * (float scale) const +{ + return Point2D(x() * scale, y() * scale); +} + +Point2D Point2D::operator + (const Point2D& other) const +{ + return Point2D(x() + other.x(), y() + other.y()); +} + Triangle::Triangle(bool t_123, const TIndices & inds): tiling(t_123), indices(inds) {} -void PenroseTiling::split(Triangle& p, std::vector& points, +Point2D Point2D::rotated(float radians) const +{ + float cosAngle = cos(radians); + float sinAngle = sin(radians); + + // Apply rotation matrix transformation + float newX = x() * cosAngle - y() * sinAngle; + float newY = x() * sinAngle + y() * cosAngle; + + return Point2D(newX, newY); +} + +/* +Point2D rotatePoint(const Point2D& point, double radians, const Point2D& origin = Point2D(0, 0)) +{ + // Define a rotate_transformer: the first template argument `2` stands for 2D, + // `float` is the coordinate type, and 2 is input and output dimension + strategy::transform::rotate_transformer rot(radians); + + Point2D rotatedPoint; + rot.apply(point, rotatedPoint); + //transform(point, rotatedPoint, rot); + + return rotatedPoint; +} +*/ + +void PenroseTiling::split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth) { uint32_t s = points.size(); @@ -50,8 +78,8 @@ void PenroseTiling::split(Triangle& p, std::vector& points, { if (p.tiling ^ !p2) { - points.push_back(glm::vec2(((1.0f - PHI) * points[i[0]]) + (PHI * points[i[2]]))); - points.push_back(glm::vec2(((1.0f - PHI) * points[i[p2]]) + (PHI * points[i[!p2]]))); + points.push_back(Point2D((points[i[0]] * (1.0f - PHI) ) + (points[i[2]]) * PHI)); + points.push_back(Point2D((points[i[p2]] * (1.0f - PHI)) + (points[i[!p2]] * PHI))); Triangle t1(p2, TIndices({ i[(!p2) + 1], p2 ? i[2] : s, p2 ? s : i[1] })); Triangle t2(true, TIndices({ p2 ? i[1] : s, s + 1, p2 ? s : i[1] })); @@ -62,7 +90,7 @@ void PenroseTiling::split(Triangle& p, std::vector& points, } else { - points.push_back(glm::vec2(((1.0f - PHI) * points[i[p2 * 2]]) + (PHI * points[i[!p2]]))); + points.push_back(Point2D((points[i[p2 * 2]] * (1.0f - PHI)) + (points[i[!p2]]) * PHI)); Triangle t1(true, TIndices({ i[2], s, i[1] })); Triangle t2(false, TIndices({ i[(!p2) + 1], s, i[0] })); @@ -95,7 +123,7 @@ void PenroseTiling::split(Triangle& p, std::vector& points, } // TODO: Return something -void generatePenroseTiling(size_t numZones, CRandomGenerator * rand); +void PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * rand) { float scale = 100.f / (numZones + 20); //TODO: Use it to initialize the large tile @@ -108,29 +136,28 @@ void generatePenroseTiling(size_t numZones, CRandomGenerator * rand); glm::vec3(d(e), d(e), d(e)), glm::vec3(d(e), d(e), d(e)) }; */ - float polyAngle = glm::radians(360.0f / POLY); + float polyAngle = 360.0f / POLY; - std::vector points = { glm::vec2(0.0f, 0.0f), glm::vec2(0.0f, 1.0f) }; + std::vector points = { Point2D(0.0f, 0.0f), Point2D(0.0f, 1.0f) }; std::array, 5> indices; for (uint32_t i = 1; i < POLY; ++i) { - //TODO: Use boost to rotate - glm::vec2 next = glm::rotate(points[i], polyAngle); + Point2D next = points[i].rotated(polyAngle); points.push_back(next); } // TODO: Scale to unit square for (auto& p : points) { - p.x = (p.x / window_w) * window_h; + p.x(p.x() * scale * BASE_SIZE); } for (uint32_t i = 0; i < POLY; i++) { std::array p = { (i % (POLY + 1)) + 1, ((i + 1) % POLY) + 1 }; - triangle t(true, TIndices({ 0, p[i & 1], p[!(i & 1)] })); + Triangle t(true, TIndices({ 0, p[i & 1], p[!(i & 1)] })); split(t, points, indices, DEPTH); } diff --git a/lib/rmg/PenroseTiling.h b/lib/rmg/PenroseTiling.h index cc3060a8a..708b425ad 100644 --- a/lib/rmg/PenroseTiling.h +++ b/lib/rmg/PenroseTiling.h @@ -14,12 +14,25 @@ #include "../CRandomGenerator.h" #include #include - +#include VCMI_LIB_NAMESPACE_BEGIN +using namespace boost::geometry; typedef std::array TIndices; +class Point2D : public model::d2::point_xy +{ +public: + using point_xy::point_xy; + + Point2D operator * (float scale) const; + Point2D operator + (const Point2D& other) const; + Point2D rotated(float radians) const; +}; + +Point2D rotatePoint(const Point2D& point, double radians, const Point2D& origin); + class Triangle { public: @@ -39,7 +52,7 @@ public: // TODO: Is that the number of symmetries? const uint32_t POLY = 10; - const float scale = 4.0f; + const float BASE_SIZE = 4.0f; //const uint32_t window_w = 1920 * scale; //const uint32_t window_h = 1080 * scale; const uint32_t DEPTH = 10; //recursion depth @@ -50,7 +63,7 @@ public: void generatePenroseTiling(size_t numZones, CRandomGenerator * rand); private: - void split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth); + void split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth); }; From 03fcfe3392703d9fef52b4dc0f6b3f4f7972b027 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Feb 2024 02:36:57 +0200 Subject: [PATCH 55/98] Use std::byte in serializer --- client/globalLobby/GlobalLobbyLoginWindow.cpp | 4 ++-- client/globalLobby/GlobalLobbyLoginWindow.h | 2 +- client/globalLobby/GlobalLobbyServerSetup.cpp | 4 ++-- client/globalLobby/GlobalLobbyServerSetup.h | 2 +- client/globalLobby/GlobalLobbyWidget.h | 2 +- lib/JsonNode.cpp | 4 ---- lib/JsonNode.h | 1 - lib/serializer/BinaryDeserializer.h | 16 ++++++++-------- lib/serializer/BinarySerializer.h | 8 ++++---- lib/serializer/CLoadFile.cpp | 4 ++-- lib/serializer/CLoadFile.h | 2 +- lib/serializer/CMemorySerializer.cpp | 4 ++-- lib/serializer/CMemorySerializer.h | 4 ++-- lib/serializer/CSaveFile.cpp | 6 +++--- lib/serializer/CSaveFile.h | 2 +- lib/serializer/CSerializer.h | 4 ++-- lib/serializer/Connection.cpp | 16 ++++++---------- lib/serializer/Connection.h | 2 +- lobby/LobbyServer.cpp | 4 ++-- 19 files changed, 41 insertions(+), 50 deletions(-) diff --git a/client/globalLobby/GlobalLobbyLoginWindow.cpp b/client/globalLobby/GlobalLobbyLoginWindow.cpp index bf7a83753..7403b206a 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.cpp +++ b/client/globalLobby/GlobalLobbyLoginWindow.cpp @@ -35,7 +35,7 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow() pos.w = 200; pos.h = 200; - background = std::make_shared(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h)); + filledBackground = std::make_shared(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h)); labelTitle = std::make_shared( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.login.title")); labelUsername = std::make_shared( 10, 45, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.lobby.login.username")); backgroundUsername = std::make_shared(Rect(10, 70, 180, 20), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64)); @@ -44,7 +44,7 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow() buttonClose = std::make_shared(Point(126, 160), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }); labelStatus = std::make_shared( "", Rect(15, 95, 175, 60), 1, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); - background->playerColored(PlayerColor(1)); + filledBackground->playerColored(PlayerColor(1)); inputUsername->setText(settings["lobby"]["displayName"].String()); inputUsername->cb += [this](const std::string & text) { diff --git a/client/globalLobby/GlobalLobbyLoginWindow.h b/client/globalLobby/GlobalLobbyLoginWindow.h index 31bcd5015..54bccc182 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.h +++ b/client/globalLobby/GlobalLobbyLoginWindow.h @@ -20,7 +20,7 @@ class CButton; class GlobalLobbyLoginWindow : public CWindowObject { - std::shared_ptr background; + std::shared_ptr filledBackground; std::shared_ptr labelTitle; std::shared_ptr labelUsername; std::shared_ptr labelStatus; diff --git a/client/globalLobby/GlobalLobbyServerSetup.cpp b/client/globalLobby/GlobalLobbyServerSetup.cpp index 8b64e2377..eb566390f 100644 --- a/client/globalLobby/GlobalLobbyServerSetup.cpp +++ b/client/globalLobby/GlobalLobbyServerSetup.cpp @@ -31,7 +31,7 @@ GlobalLobbyServerSetup::GlobalLobbyServerSetup() pos.w = 284; pos.h = 340; - background = std::make_shared(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h)); + filledBackground = std::make_shared(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h)); labelTitle = std::make_shared( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.create")); labelPlayerLimit = std::make_shared( pos.w / 2, 48, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.players.limit")); labelRoomType = std::make_shared( pos.w / 2, 108, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.room.type")); @@ -75,7 +75,7 @@ GlobalLobbyServerSetup::GlobalLobbyServerSetup() buttonCreate = std::make_shared(Point(10, 300), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onCreate(); }); buttonClose = std::make_shared(Point(210, 300), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }); - background->playerColored(PlayerColor(1)); + filledBackground->playerColored(PlayerColor(1)); updateDescription(); center(); diff --git a/client/globalLobby/GlobalLobbyServerSetup.h b/client/globalLobby/GlobalLobbyServerSetup.h index 7f49bb130..f8fa09d34 100644 --- a/client/globalLobby/GlobalLobbyServerSetup.h +++ b/client/globalLobby/GlobalLobbyServerSetup.h @@ -19,7 +19,7 @@ class CToggleGroup; class GlobalLobbyServerSetup : public CWindowObject { - std::shared_ptr background; + std::shared_ptr filledBackground; std::shared_ptr labelTitle; std::shared_ptr labelPlayerLimit; diff --git a/client/globalLobby/GlobalLobbyWidget.h b/client/globalLobby/GlobalLobbyWidget.h index 4942add0a..61caaaa21 100644 --- a/client/globalLobby/GlobalLobbyWidget.h +++ b/client/globalLobby/GlobalLobbyWidget.h @@ -24,7 +24,7 @@ class GlobalLobbyWidget : public InterfaceObjectConfigurable std::shared_ptr buildRoomList(const JsonNode &) const; public: - GlobalLobbyWidget(GlobalLobbyWindow * window); + explicit GlobalLobbyWidget(GlobalLobbyWindow * window); std::shared_ptr getAccountNameLabel(); std::shared_ptr getMessageInput(); diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index 4124bd818..f0a6334f8 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -72,10 +72,6 @@ JsonNode::JsonNode(JsonType Type) setType(Type); } -JsonNode::JsonNode(const uint8_t *data, size_t datasize) - :JsonNode(reinterpret_cast(data), datasize) -{} - JsonNode::JsonNode(const std::byte *data, size_t datasize) :JsonNode(reinterpret_cast(data), datasize) {} diff --git a/lib/JsonNode.h b/lib/JsonNode.h index bd9de7886..75f9128b2 100644 --- a/lib/JsonNode.h +++ b/lib/JsonNode.h @@ -51,7 +51,6 @@ public: JsonNode(JsonType Type = JsonType::DATA_NULL); //Create tree from Json-formatted input explicit JsonNode(const char * data, size_t datasize); - explicit JsonNode(const uint8_t * data, size_t datasize); explicit JsonNode(const std::byte * data, size_t datasize); //Create tree from JSON file explicit JsonNode(const JsonPath & fileURI); diff --git a/lib/serializer/BinaryDeserializer.h b/lib/serializer/BinaryDeserializer.h index b34addee5..222ed2364 100644 --- a/lib/serializer/BinaryDeserializer.h +++ b/lib/serializer/BinaryDeserializer.h @@ -23,9 +23,13 @@ protected: public: CLoaderBase(IBinaryReader * r): reader(r){}; - inline int read(void * data, unsigned size) + inline void read(void * data, unsigned size, bool reverseEndianess) { - return reader->read(data, size); + auto bytePtr = reinterpret_cast(data); + + reader->read(bytePtr, size); + if(reverseEndianess) + std::reverse(bytePtr, bytePtr + size); }; }; @@ -170,11 +174,7 @@ public: template < class T, typename std::enable_if < std::is_fundamental::value && !std::is_same::value, int >::type = 0 > void load(T &data) { - unsigned length = sizeof(data); - char * dataPtr = reinterpret_cast(&data); - this->read(dataPtr,length); - if(reverseEndianess) - std::reverse(dataPtr, dataPtr + length); + this->read(static_cast(&data), sizeof(data), reverseEndianess); } template < typename T, typename std::enable_if < is_serializeable::value, int >::type = 0 > @@ -439,7 +439,7 @@ public: { ui32 length = readAndCheckLength(); data.resize(length); - this->read((void*)data.c_str(),length); + this->read(static_cast(data.data()), length, false); } template diff --git a/lib/serializer/BinarySerializer.h b/lib/serializer/BinarySerializer.h index 9f465e539..07d152ad3 100644 --- a/lib/serializer/BinarySerializer.h +++ b/lib/serializer/BinarySerializer.h @@ -23,9 +23,9 @@ protected: public: CSaverBase(IBinaryWriter * w): writer(w){}; - inline int write(const void * data, unsigned size) + inline void write(const void * data, unsigned size) { - return writer->write(data, size); + writer->write(reinterpret_cast(data), size); }; }; @@ -145,7 +145,7 @@ public: void save(const T &data) { // save primitive - simply dump binary data to output - this->write(&data,sizeof(data)); + this->write(static_cast(&data), sizeof(data)); } template < typename T, typename std::enable_if < std::is_enum::value, int >::type = 0 > @@ -312,7 +312,7 @@ public: void save(const std::string &data) { save(ui32(data.length())); - this->write(data.c_str(),(unsigned int)data.size()); + this->write(static_cast(data.data()), data.size()); } template void save(const std::pair &data) diff --git a/lib/serializer/CLoadFile.cpp b/lib/serializer/CLoadFile.cpp index 2e50fa6c1..ef43b8e27 100644 --- a/lib/serializer/CLoadFile.cpp +++ b/lib/serializer/CLoadFile.cpp @@ -21,7 +21,7 @@ CLoadFile::CLoadFile(const boost::filesystem::path & fname, ESerializationVersio //must be instantiated in .cpp file for access to complete types of all member fields CLoadFile::~CLoadFile() = default; -int CLoadFile::read(void * data, unsigned size) +int CLoadFile::read(std::byte * data, unsigned size) { sfile->read(reinterpret_cast(data), size); return size; @@ -92,7 +92,7 @@ void CLoadFile::clear() void CLoadFile::checkMagicBytes(const std::string &text) { std::string loaded = text; - read((void *)loaded.data(), static_cast(text.length())); + read(reinterpret_cast(loaded.data()), text.length()); if(loaded != text) throw std::runtime_error("Magic bytes doesn't match!"); } diff --git a/lib/serializer/CLoadFile.h b/lib/serializer/CLoadFile.h index 405c1f99c..800872038 100644 --- a/lib/serializer/CLoadFile.h +++ b/lib/serializer/CLoadFile.h @@ -23,7 +23,7 @@ public: CLoadFile(const boost::filesystem::path & fname, ESerializationVersion minimalVersion = ESerializationVersion::CURRENT); //throws! virtual ~CLoadFile(); - int read(void * data, unsigned size) override; //throws! + int read(std::byte * data, unsigned size) override; //throws! void openNextFile(const boost::filesystem::path & fname, ESerializationVersion minimalVersion); //throws! void clear(); diff --git a/lib/serializer/CMemorySerializer.cpp b/lib/serializer/CMemorySerializer.cpp index 39a647335..fd7346793 100644 --- a/lib/serializer/CMemorySerializer.cpp +++ b/lib/serializer/CMemorySerializer.cpp @@ -12,7 +12,7 @@ VCMI_LIB_NAMESPACE_BEGIN -int CMemorySerializer::read(void * data, unsigned size) +int CMemorySerializer::read(std::byte * data, unsigned size) { if(buffer.size() < readPos + size) throw std::runtime_error(boost::str(boost::format("Cannot read past the buffer (accessing index %d, while size is %d)!") % (readPos + size - 1) % buffer.size())); @@ -22,7 +22,7 @@ int CMemorySerializer::read(void * data, unsigned size) return size; } -int CMemorySerializer::write(const void * data, unsigned size) +int CMemorySerializer::write(const std::byte * data, unsigned size) { auto oldSize = buffer.size(); //and the pos to write from buffer.resize(oldSize + size); diff --git a/lib/serializer/CMemorySerializer.h b/lib/serializer/CMemorySerializer.h index 586220724..510012d29 100644 --- a/lib/serializer/CMemorySerializer.h +++ b/lib/serializer/CMemorySerializer.h @@ -25,8 +25,8 @@ public: BinaryDeserializer iser; BinarySerializer oser; - int read(void * data, unsigned size) override; //throws! - int write(const void * data, unsigned size) override; + int read(std::byte * data, unsigned size) override; //throws! + int write(const std::byte * data, unsigned size) override; CMemorySerializer(); diff --git a/lib/serializer/CSaveFile.cpp b/lib/serializer/CSaveFile.cpp index e575a88a3..d406342b4 100644 --- a/lib/serializer/CSaveFile.cpp +++ b/lib/serializer/CSaveFile.cpp @@ -21,9 +21,9 @@ CSaveFile::CSaveFile(const boost::filesystem::path &fname) //must be instantiated in .cpp file for access to complete types of all member fields CSaveFile::~CSaveFile() = default; -int CSaveFile::write(const void * data, unsigned size) +int CSaveFile::write(const std::byte * data, unsigned size) { - sfile->write((char *)data,size); + sfile->write(reinterpret_cast(data), size); return size; } @@ -66,7 +66,7 @@ void CSaveFile::clear() void CSaveFile::putMagicBytes(const std::string &text) { - write(text.c_str(), static_cast(text.length())); + write(reinterpret_cast(text.c_str()), text.length()); } VCMI_LIB_NAMESPACE_END diff --git a/lib/serializer/CSaveFile.h b/lib/serializer/CSaveFile.h index f1b823bf2..857b7f159 100644 --- a/lib/serializer/CSaveFile.h +++ b/lib/serializer/CSaveFile.h @@ -23,7 +23,7 @@ public: CSaveFile(const boost::filesystem::path &fname); //throws! ~CSaveFile(); - int write(const void * data, unsigned size) override; + int write(const std::byte * data, unsigned size) override; void openNextFile(const boost::filesystem::path &fname); //throws! void clear(); diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index 6ab715d3c..d0f90c758 100644 --- a/lib/serializer/CSerializer.h +++ b/lib/serializer/CSerializer.h @@ -175,14 +175,14 @@ struct VectorizedIDType class DLL_LINKAGE IBinaryReader : public virtual CSerializer { public: - virtual int read(void * data, unsigned size) = 0; + virtual int read(std::byte * data, unsigned size) = 0; }; /// Base class for serializers class DLL_LINKAGE IBinaryWriter : public virtual CSerializer { public: - virtual int write(const void * data, unsigned size) = 0; + virtual int write(const std::byte * data, unsigned size) = 0; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index 33a25363e..cc6c83719 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -24,7 +24,7 @@ class DLL_LINKAGE ConnectionPackWriter final : public IBinaryWriter public: std::vector buffer; - int write(const void * data, unsigned size) final; + int write(const std::byte * data, unsigned size) final; }; class DLL_LINKAGE ConnectionPackReader final : public IBinaryReader @@ -33,25 +33,21 @@ public: const std::vector * buffer; size_t position; - int read(void * data, unsigned size) final; + int read(std::byte * data, unsigned size) final; }; -int ConnectionPackWriter::write(const void * data, unsigned size) +int ConnectionPackWriter::write(const std::byte * data, unsigned size) { - const std::byte * begin_ptr = static_cast(data); - const std::byte * end_ptr = begin_ptr + size; - buffer.insert(buffer.end(), begin_ptr, end_ptr); + buffer.insert(buffer.end(), data, data + size); return size; } -int ConnectionPackReader::read(void * data, unsigned size) +int ConnectionPackReader::read(std::byte * data, unsigned size) { if (position + size > buffer->size()) throw std::runtime_error("End of file reached when reading received network pack!"); - std::byte * begin_ptr = static_cast(data); - - std::copy_n(buffer->begin() + position, size, begin_ptr); + std::copy_n(buffer->begin() + position, size, data); position += size; return size; } diff --git a/lib/serializer/Connection.h b/lib/serializer/Connection.h index 067ab415d..5e18bbbf7 100644 --- a/lib/serializer/Connection.h +++ b/lib/serializer/Connection.h @@ -46,7 +46,7 @@ public: std::string uuid; int connectionID; - CConnection(std::weak_ptr networkConnection); + explicit CConnection(std::weak_ptr networkConnection); ~CConnection(); void sendPack(const CPack * pack); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 5cf741e7e..551425025 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -205,10 +205,10 @@ void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection) void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection, const std::string & errorMessage) { - if (activeAccounts.count(connection)) + if(activeAccounts.count(connection)) database->setAccountOnline(activeAccounts.at(connection), false); - if (activeGameRooms.count(connection)) + if(activeGameRooms.count(connection)) database->setGameRoomStatus(activeGameRooms.at(connection), LobbyRoomState::CLOSED); // NOTE: lost connection can be in only one of these lists (or in none of them) From 178f960533d00d38adfa0c5206101a890249c441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 2 Feb 2024 14:27:32 +0100 Subject: [PATCH 56/98] First working version --- lib/rmg/CZonePlacer.cpp | 84 ++++++++++++---- lib/rmg/PenroseTiling.cpp | 203 +++++++++++++------------------------- lib/rmg/PenroseTiling.h | 15 +-- 3 files changed, 142 insertions(+), 160 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 44e372dbe..383370ab5 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -20,6 +20,7 @@ #include "RmgMap.h" #include "Zone.h" #include "Functions.h" +#include "PenroseTiling.h" VCMI_LIB_NAMESPACE_BEGIN @@ -524,7 +525,7 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const std::vector prescaler = { 0, 0 }; for (int i = 0; i < 2; i++) - prescaler[i] = std::sqrt((width * height) / (totalSize[i] * 3.14f)); + prescaler[i] = std::sqrt((width * height) / (totalSize[i] * PI_CONSTANT)); mapSize = static_cast(sqrt(width * height)); for(const auto & zone : zones) { @@ -802,6 +803,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista float CZonePlacer::metric (const int3 &A, const int3 &B) const { + /* float dx = abs(A.x - B.x) * scaleX; float dy = abs(A.y - B.y) * scaleY; @@ -811,9 +813,14 @@ float CZonePlacer::metric (const int3 &A, const int3 &B) const 3. Nonlinear mess for fuzzy edges */ + /* return dx * dx + dy * dy + 5 * std::sin(dx * dy / 10) + 25 * std::sin (std::sqrt(A.x * B.x) * (A.y - B.y) / 100 * (scaleX * scaleY)); + */ + + return A.dist2dSQ(B); + } void CZonePlacer::assignZones(CRandomGenerator * rand) @@ -845,7 +852,13 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize(); }; - auto moveZoneToCenterOfMass = [](const std::shared_ptr & zone) -> void + auto simpleCompareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool + { + //bigger zones have smaller distance + return lhs.second < rhs.second; + }; + + auto moveZoneToCenterOfMass = [width, height](const std::shared_ptr & zone) -> void { int3 total(0, 0, 0); auto tiles = zone->area().getTiles(); @@ -855,17 +868,17 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) } int size = static_cast(tiles.size()); assert(size); - zone->setPos(int3(total.x / size, total.y / size, total.z / size)); + auto newPos = int3(total.x / size, total.y / size, total.z / size); + zone->setPos(newPos); + zone->setCenter(float3(float(newPos.x) / width, float(newPos.y) / height, newPos.z)); }; int levels = map.levels(); - /* - 1. Create Voronoi diagram - 2. find current center of mass for each zone. Move zone to that center to balance zones sizes - */ + // Find current center of mass for each zone. Move zone to that center to balance zones sizes int3 pos; + for(pos.z = 0; pos.z < levels; pos.z++) { for(pos.x = 0; pos.x < width; pos.x++) @@ -890,14 +903,41 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) if(zone.second->area().empty()) throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size"); + // FIXME: Is 2. correct and doesn't break balance? moveZoneToCenterOfMass(zone.second); } - //assign actual tiles to each zone using nonlinear norm for fine edges - for(const auto & zone : zones) zone.second->clearTiles(); //now populate them again + // Assign zones to closest Penrose vertex + PenroseTiling penrose; + auto vertices = penrose.generatePenroseTiling(zones.size(), rand); + + std::map, std::set> vertexMapping; + + for (const auto & vertex : vertices) + { + distances.clear(); + for(const auto & zone : zones) + { + distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), 0))); + } + auto closestZone = boost::min_element(distances, compareByDistance)->first; + + vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, closestZone->getPos().z)); //Closest vertex belongs to zone + } + + for (const auto & p : vertexMapping) + { + for (const auto vertex : p.second) + { + logGlobal->info("Zone %2d is assigned to vertex %s", p.first->getId(), vertex.toString()); + } + logGlobal->info("Zone %2d has total of %d vertices", p.first->getId(), p.second.size()); + } + + //Assign actual tiles to each zone using nonlinear norm for fine edges for (pos.z = 0; pos.z < levels; pos.z++) { for (pos.x = 0; pos.x < width; pos.x++) @@ -905,22 +945,32 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) for (pos.y = 0; pos.y < height; pos.y++) { distances.clear(); - for(const auto & zone : zones) + for(const auto & zoneVertex : vertexMapping) { - if (zone.second->getPos().z == pos.z) - distances.emplace_back(zone.second, metric(pos, zone.second->getPos())); - else - distances.emplace_back(zone.second, std::numeric_limits::max()); + // FIXME: Find closest vertex, not closest zone + auto zone = zoneVertex.first; + for (const auto & vertex : zoneVertex.second) + { + if (zone->getCenter().z == pos.z) + distances.emplace_back(zone, metric(pos, vertex)); + else + distances.emplace_back(zone, std::numeric_limits::max()); + } } - auto zone = boost::min_element(distances, compareByDistance)->first; //closest tile belongs to zone - zone->area().add(pos); - map.setZoneID(pos, zone->getId()); + + auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first; //closest tile belongs to zone + closestZone->area().add(pos); + map.setZoneID(pos, closestZone->getId()); } } } + //set position (town position) to center of mass of irregular zone for(const auto & zone : zones) { + if(zone.second->area().empty()) + throw rmgException("Empty zone after Penrose tiling"); + moveZoneToCenterOfMass(zone.second); //TODO: similiar for islands diff --git a/lib/rmg/PenroseTiling.cpp b/lib/rmg/PenroseTiling.cpp index 70d6696ee..84a1404aa 100644 --- a/lib/rmg/PenroseTiling.cpp +++ b/lib/rmg/PenroseTiling.cpp @@ -1,28 +1,20 @@ /* - * © 2020 Michael Percival - * See LICENSE file for copyright and license details. + * PenroseTiling.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * */ -// Adapted from https://github.com/mpizzzle/penrose - -//https://www.boost.org/doc/libs/1_72_0/libs/geometry/doc/html/geometry/reference/adapted/boost_polygon/point_data.html - -//#include -//#include - -//#include -//#include +// Adapted from https://github.com/mpizzzle/penrose by Michael Percival #include "StdInc.h" #include "PenroseTiling.h" - -//#include "shader.hpp" -//#include "png_writer.hpp" - VCMI_LIB_NAMESPACE_BEGIN -//static const std::string file_name = "penrose.png"; Point2D Point2D::operator * (float scale) const { @@ -34,11 +26,35 @@ Point2D Point2D::operator + (const Point2D& other) const return Point2D(x() + other.x(), y() + other.y()); } +bool Point2D::operator < (const Point2D& other) const +{ + if (x() < other.x()) + { + return true; + } + else + { + return y() < other.y(); + } +} + Triangle::Triangle(bool t_123, const TIndices & inds): tiling(t_123), indices(inds) {} +Triangle::~Triangle() +{ + for (auto * triangle : subTriangles) + { + if (triangle) + { + delete triangle; + triangle = nullptr; + } + } +} + Point2D Point2D::rotated(float radians) const { float cosAngle = cos(radians); @@ -51,21 +67,6 @@ Point2D Point2D::rotated(float radians) const return Point2D(newX, newY); } -/* -Point2D rotatePoint(const Point2D& point, double radians, const Point2D& origin = Point2D(0, 0)) -{ - // Define a rotate_transformer: the first template argument `2` stands for 2D, - // `float` is the coordinate type, and 2 is input and output dimension - strategy::transform::rotate_transformer rot(radians); - - Point2D rotatedPoint; - rot.apply(point, rotatedPoint); - //transform(point, rotatedPoint, rot); - - return rotatedPoint; -} -*/ - void PenroseTiling::split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth) { @@ -81,21 +82,20 @@ void PenroseTiling::split(Triangle& p, std::vector& points, points.push_back(Point2D((points[i[0]] * (1.0f - PHI) ) + (points[i[2]]) * PHI)); points.push_back(Point2D((points[i[p2]] * (1.0f - PHI)) + (points[i[!p2]] * PHI))); - Triangle t1(p2, TIndices({ i[(!p2) + 1], p2 ? i[2] : s, p2 ? s : i[1] })); - Triangle t2(true, TIndices({ p2 ? i[1] : s, s + 1, p2 ? s : i[1] })); - Triangle t3(false, TIndices({ s, s + 1, i[0] })); + auto * t1 = new Triangle(p2, TIndices({ i[(!p2) + 1], p2 ? i[2] : s, p2 ? s : i[1] })); + auto * t2 = new Triangle(true, TIndices({ p2 ? i[1] : s, s + 1, p2 ? s : i[1] })); + auto * t3 = new Triangle(false, TIndices({ s, s + 1, i[0] })); - // FIXME: Make sure these are not destroyed when we leave the scope - p.subTriangles = { &t1, &t2, &t3 }; + p.subTriangles = { t1, t2, t3 }; } else { points.push_back(Point2D((points[i[p2 * 2]] * (1.0f - PHI)) + (points[i[!p2]]) * PHI)); - Triangle t1(true, TIndices({ i[2], s, i[1] })); - Triangle t2(false, TIndices({ i[(!p2) + 1], s, i[0] })); + auto * t1 = new Triangle(true, TIndices({ i[2], s, i[1] })); + auto * t2 = new Triangle(false, TIndices({ i[(!p2) + 1], s, i[0] })); - p.subTriangles = { &t1, &t2 }; + p.subTriangles = { t1, t2 }; } for (auto& t : p.subTriangles) @@ -122,23 +122,14 @@ void PenroseTiling::split(Triangle& p, std::vector& points, return; } -// TODO: Return something -void PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * rand) +std::set PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * rand) { - float scale = 100.f / (numZones + 20); //TODO: Use it to initialize the large tile + float scale = 100.f / (numZones + 20); + float polyAngle = (2 * PI_CONSTANT) / POLY; - //static std::default_random_engine e(std::random_device{}()); - static std::uniform_real_distribution<> d(0, 1); + float randomAngle = rand->nextDouble(0, 2 * PI_CONSTANT); - /* - std::vector colours = { glm::vec3(d(e), d(e), d(e)), glm::vec3(d(e), d(e), d(e)), - glm::vec3(d(e), d(e), d(e)), glm::vec3(d(e), d(e), d(e)), - glm::vec3(d(e), d(e), d(e)), glm::vec3(d(e), d(e), d(e)) }; - */ - - float polyAngle = 360.0f / POLY; - - std::vector points = { Point2D(0.0f, 0.0f), Point2D(0.0f, 1.0f) }; + std::vector points = { Point2D(0.0f, 0.0f), Point2D(0.0f, 1.0f).rotated(randomAngle) }; std::array, 5> indices; for (uint32_t i = 1; i < POLY; ++i) @@ -147,12 +138,21 @@ void PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * ra points.push_back(next); } - // TODO: Scale to unit square for (auto& p : points) { p.x(p.x() * scale * BASE_SIZE); } + // Scale square to window size + /* + for (auto& p : points) + { + p.x = (p.x / window_w) * window_h; + } + */ + + std::set finalPoints; + for (uint32_t i = 0; i < POLY; i++) { std::array p = { (i % (POLY + 1)) + 1, ((i + 1) % POLY) + 1 }; @@ -162,93 +162,22 @@ void PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * ra split(t, points, indices, DEPTH); } - // TODO: Return collection of triangles - // TODO: Get center point of the triangle - - // Do not draw anything /* - if(!glfwInit()) + //No difference for the number of points + for (auto& p : points) { - return -1; + p = p + Point2D(0.5f, 0.5f); // Center in a square (0,1) } - - glfwWindowHint(GLFW_SAMPLES, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - - GLFWwindow* window = glfwCreateWindow(window_w, window_h, "penrose", NULL, NULL); - - if(window == NULL) { - glfwTerminate(); - return -1; - } - - glfwMakeContextCurrent(window); - glewExperimental=true; - - if (glewInit() != GLEW_OK) { - return -1; - } - - glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE); - - uint32_t VAOs[5], VBO, EBOs[5]; - - glGenVertexArrays(5, VAOs); - glGenBuffers(1, &VBO); - glGenBuffers(5, EBOs); - glLineWidth(line_w); - - for (uint32_t i = 0; i < indices.size(); ++i) { - glBindVertexArray(VAOs[i]); - - glBindBuffer(GL_ARRAY_BUFFER, VBO); - glBufferData(GL_ARRAY_BUFFER, points.size() * 4 * 2, &points[0], GL_STATIC_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBOs[i]); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices[i].size() * 4, &indices[i][0], GL_STATIC_DRAW); - - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); - glEnableVertexAttribArray(0); - } - - uint32_t programID = Shader::loadShaders("vertex.vert", "fragment.frag"); - GLint paint = glGetUniformLocation(programID, "paint"); - - while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose(window) == 0 && paint != -1) { - glViewport(-1.0 * (window_w / scale) * ((0.5 * scale) - 0.5), -1.0 * (window_h / scale) * ((0.5 * scale) - 0.5), window_w, window_h); - glClearColor(colours.back().x, colours.back().y, colours.back().z, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glUseProgram(programID); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - - for (uint32_t i = 0; i < indices.size(); ++i) { - glPolygonMode(GL_FRONT_AND_BACK, i < indices.size() - 1 ? GL_FILL : GL_LINE); - glUniform3fv(paint, 1, &colours[i][0]); - glBindVertexArray(VAOs[i]); - glDrawElements(i < indices.size() - 1 ? GL_TRIANGLES : GL_LINES, indices[i].size(), GL_UNSIGNED_INT, 0); - } - - glfwSwapBuffers(window); - glfwPollEvents(); - } - - int frame_w, frame_h; - glfwGetFramebufferSize(window, &frame_w, &frame_h); - - png_bytep* row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * frame_h); - - for (int y = 0; y < frame_h; ++y) { - row_pointers[y] = (png_byte*) malloc((4 * sizeof(png_byte)) * frame_w); - glReadPixels(0, y, frame_w, 1, GL_RGBA, GL_UNSIGNED_BYTE, row_pointers[y]); - } - - PngWriter::write_png_file(file_name, frame_w, frame_h, row_pointers); - - return 0; */ + + vstd::copy_if(points, vstd::set_inserter(finalPoints), [](const Point2D point) + { + return vstd::isbetween(point.x(), 0.f, 1.0f) && vstd::isbetween(point.y(), 0.f, 1.0f); + }); + + logGlobal->info("Number of points within unit square: %d", finalPoints.size()); + + return finalPoints; } VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/PenroseTiling.h b/lib/rmg/PenroseTiling.h index 708b425ad..3288f4dfb 100644 --- a/lib/rmg/PenroseTiling.h +++ b/lib/rmg/PenroseTiling.h @@ -21,6 +21,8 @@ VCMI_LIB_NAMESPACE_BEGIN using namespace boost::geometry; typedef std::array TIndices; +const float PI_CONSTANT = 3.141592f; + class Point2D : public model::d2::point_xy { public: @@ -29,6 +31,8 @@ public: Point2D operator * (float scale) const; Point2D operator + (const Point2D& other) const; Point2D rotated(float radians) const; + + bool operator < (const Point2D& other) const; }; Point2D rotatePoint(const Point2D& point, double radians, const Point2D& origin); @@ -36,6 +40,8 @@ Point2D rotatePoint(const Point2D& point, double radians, const Point2D& origin) class Triangle { public: + ~Triangle(); + const bool tiling; TIndices indices; @@ -52,15 +58,12 @@ public: // TODO: Is that the number of symmetries? const uint32_t POLY = 10; - const float BASE_SIZE = 4.0f; - //const uint32_t window_w = 1920 * scale; - //const uint32_t window_h = 1080 * scale; - const uint32_t DEPTH = 10; //recursion depth + const float BASE_SIZE = 1.f; + const uint32_t DEPTH = 7; //Recursion depth const bool P2 = false; // Tiling type - //const float line_w = 2.0f; //line width - void generatePenroseTiling(size_t numZones, CRandomGenerator * rand); + std::set generatePenroseTiling(size_t numZones, CRandomGenerator * rand); private: void split(Triangle& p, std::vector& points, std::array, 5>& indices, uint32_t depth); From f97ffd8e9a572cef2cd4d50b8e153488bc298b93 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Feb 2024 15:32:06 +0200 Subject: [PATCH 57/98] Better handling of disconnects, code cleanup --- lib/network/NetworkConnection.cpp | 17 +++++++--- lib/network/NetworkConnection.h | 3 +- lib/network/NetworkInterface.h | 3 +- lib/network/NetworkServer.cpp | 21 ++---------- lib/network/NetworkServer.h | 3 -- lib/serializer/CMemorySerializer.cpp | 4 +-- lib/serializer/CMemorySerializer.h | 2 +- lobby/LobbyDatabase.cpp | 36 +++++++------------- lobby/LobbyDatabase.h | 9 ++--- lobby/LobbyDefines.h | 1 - lobby/LobbyServer.cpp | 50 +++++++++++++++------------- lobby/LobbyServer.h | 2 +- server/CVCMIServer.cpp | 4 +-- server/GlobalLobbyProcessor.cpp | 12 +++++-- 14 files changed, 76 insertions(+), 91 deletions(-) diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index f8a0ae9f1..ebeb06778 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -26,7 +26,7 @@ void NetworkConnection::start() boost::asio::async_read(*socket, readBuffer, boost::asio::transfer_exactly(messageHeaderSize), - [this](const auto & ec, const auto & endpoint) { onHeaderReceived(ec); }); + [self = shared_from_this()](const auto & ec, const auto & endpoint) { self->onHeaderReceived(ec); }); } void NetworkConnection::onHeaderReceived(const boost::system::error_code & ec) @@ -42,7 +42,7 @@ void NetworkConnection::onHeaderReceived(const boost::system::error_code & ec) boost::asio::async_read(*socket, readBuffer, boost::asio::transfer_exactly(messageSize), - [this, messageSize](const auto & ec, const auto & endpoint) { onPacketReceived(ec, messageSize); }); + [self = shared_from_this(), messageSize](const auto & ec, const auto & endpoint) { self->onPacketReceived(ec, messageSize); }); } uint32_t NetworkConnection::readPacketSize() @@ -54,7 +54,7 @@ uint32_t NetworkConnection::readPacketSize() readBuffer.sgetn(reinterpret_cast(&messageSize), sizeof(messageSize)); if (messageSize > messageMaxSize) - throw std::runtime_error("Invalid packet size!"); + listener.onDisconnected(shared_from_this(), "Invalid packet size!"); return messageSize; } @@ -88,7 +88,16 @@ void NetworkConnection::sendPacket(const std::vector & message) boost::asio::write(*socket, boost::asio::buffer(messageSize), ec ); boost::asio::write(*socket, boost::asio::buffer(message), ec ); - // FIXME: handle error? + if (ec) + listener.onDisconnected(shared_from_this(), ec.message()); +} + +void NetworkConnection::close() +{ + boost::system::error_code ec; + socket->close(ec); + + //NOTE: ignoring error code } VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h index 76b032bf2..e445329d1 100644 --- a/lib/network/NetworkConnection.h +++ b/lib/network/NetworkConnection.h @@ -13,7 +13,7 @@ VCMI_LIB_NAMESPACE_BEGIN -class NetworkConnection : public INetworkConnection, public std::enable_shared_from_this +class NetworkConnection : public INetworkConnection, std::enable_shared_from_this { static const int messageHeaderSize = sizeof(uint32_t); static const int messageMaxSize = 64 * 1024 * 1024; // arbitrary size to prevent potential massive allocation if we receive garbage input @@ -31,6 +31,7 @@ public: NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr & socket); void start(); + void close() override; void sendPacket(const std::vector & message) override; }; diff --git a/lib/network/NetworkInterface.h b/lib/network/NetworkInterface.h index ba1752d56..58bd11023 100644 --- a/lib/network/NetworkInterface.h +++ b/lib/network/NetworkInterface.h @@ -17,6 +17,7 @@ class DLL_LINKAGE INetworkConnection : boost::noncopyable public: virtual ~INetworkConnection() = default; virtual void sendPacket(const std::vector & message) = 0; + virtual void close() = 0; }; using NetworkConnectionPtr = std::shared_ptr; @@ -38,8 +39,6 @@ class DLL_LINKAGE INetworkServer : boost::noncopyable public: virtual ~INetworkServer() = default; - virtual void sendPacket(const std::shared_ptr &, const std::vector & message) = 0; - virtual void closeConnection(const std::shared_ptr &) = 0; virtual void start(uint16_t port) = 0; }; diff --git a/lib/network/NetworkServer.cpp b/lib/network/NetworkServer.cpp index 676764c3e..b408ed525 100644 --- a/lib/network/NetworkServer.cpp +++ b/lib/network/NetworkServer.cpp @@ -28,7 +28,7 @@ void NetworkServer::start(uint16_t port) void NetworkServer::startAsyncAccept() { auto upcomingConnection = std::make_shared(*io); - acceptor->async_accept(*upcomingConnection, std::bind(&NetworkServer::connectionAccepted, this, upcomingConnection, _1)); + acceptor->async_accept(*upcomingConnection, [this, upcomingConnection](const auto & ec) { connectionAccepted(upcomingConnection, ec); }); } void NetworkServer::connectionAccepted(std::shared_ptr upcomingConnection, const boost::system::error_code & ec) @@ -46,27 +46,12 @@ void NetworkServer::connectionAccepted(std::shared_ptr upcomingCo startAsyncAccept(); } -void NetworkServer::sendPacket(const std::shared_ptr & connection, const std::vector & message) -{ - connection->sendPacket(message); -} - -void NetworkServer::closeConnection(const std::shared_ptr & connection) -{ - logNetwork->info("Closing connection!"); - assert(connections.count(connection)); - connections.erase(connection); -} - void NetworkServer::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { logNetwork->info("Connection lost! Reason: %s", errorMessage); assert(connections.count(connection)); - if (connections.count(connection)) // how? Connection was explicitly closed before? - { - connections.erase(connection); - listener.onDisconnected(connection, errorMessage); - } + connections.erase(connection); + listener.onDisconnected(connection, errorMessage); } void NetworkServer::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) diff --git a/lib/network/NetworkServer.h b/lib/network/NetworkServer.h index 805adfba5..8fc0e8988 100644 --- a/lib/network/NetworkServer.h +++ b/lib/network/NetworkServer.h @@ -29,9 +29,6 @@ class NetworkServer : public INetworkConnectionListener, public INetworkServer public: NetworkServer(INetworkServerListener & listener, const std::shared_ptr & context); - void sendPacket(const std::shared_ptr &, const std::vector & message) override; - void closeConnection(const std::shared_ptr &) override; - void start(uint16_t port) override; }; diff --git a/lib/serializer/CMemorySerializer.cpp b/lib/serializer/CMemorySerializer.cpp index fd7346793..13f3a59cf 100644 --- a/lib/serializer/CMemorySerializer.cpp +++ b/lib/serializer/CMemorySerializer.cpp @@ -17,7 +17,7 @@ int CMemorySerializer::read(std::byte * data, unsigned size) if(buffer.size() < readPos + size) throw std::runtime_error(boost::str(boost::format("Cannot read past the buffer (accessing index %d, while size is %d)!") % (readPos + size - 1) % buffer.size())); - std::memcpy(data, buffer.data() + readPos, size); + std::copy_n(buffer.data() + readPos, size, data); readPos += size; return size; } @@ -26,7 +26,7 @@ int CMemorySerializer::write(const std::byte * data, unsigned size) { auto oldSize = buffer.size(); //and the pos to write from buffer.resize(oldSize + size); - std::memcpy(buffer.data() + oldSize, data, size); + std::copy_n(data, size, buffer.data() + oldSize); return size; } diff --git a/lib/serializer/CMemorySerializer.h b/lib/serializer/CMemorySerializer.h index 510012d29..caaa4cc24 100644 --- a/lib/serializer/CMemorySerializer.h +++ b/lib/serializer/CMemorySerializer.h @@ -18,7 +18,7 @@ VCMI_LIB_NAMESPACE_BEGIN class DLL_LINKAGE CMemorySerializer : public IBinaryReader, public IBinaryWriter { - std::vector buffer; + std::vector buffer; size_t readPos; //index of the next byte to be read public: diff --git a/lobby/LobbyDatabase.cpp b/lobby/LobbyDatabase.cpp index 47ad5b88a..308089631 100644 --- a/lobby/LobbyDatabase.cpp +++ b/lobby/LobbyDatabase.cpp @@ -149,12 +149,6 @@ void LobbyDatabase::prepareStatements() WHERE roomID = ? )"; - static const std::string setGameRoomPlayerLimitText = R"( - UPDATE gameRooms - SET playerLimit = ? - WHERE roomID = ? - )"; - // SELECT FROM static const std::string getRecentMessageHistoryText = R"( @@ -221,7 +215,7 @@ void LobbyDatabase::prepareStatements() static const std::string isAccountCookieValidText = R"( SELECT COUNT(accountID) FROM accountCookies - WHERE accountID = ? AND cookieUUID = ? AND strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',creationTime) < ? + WHERE accountID = ? AND cookieUUID = ? )"; static const std::string isGameRoomCookieValidText = R"( @@ -269,7 +263,6 @@ void LobbyDatabase::prepareStatements() setAccountOnlineStatement = database->prepare(setAccountOnlineText); setGameRoomStatusStatement = database->prepare(setGameRoomStatusText); - setGameRoomPlayerLimitStatement = database->prepare(setGameRoomPlayerLimitText); getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText); getIdleGameRoomStatement = database->prepare(getIdleGameRoomText); @@ -352,11 +345,6 @@ void LobbyDatabase::setGameRoomStatus(const std::string & roomID, LobbyRoomState setGameRoomStatusStatement->executeOnce(vstd::to_underlying(roomStatus), roomID); } -void LobbyDatabase::setGameRoomPlayerLimit(const std::string & roomID, uint32_t playerLimit) -{ - setGameRoomPlayerLimitStatement->executeOnce(playerLimit, roomID); -} - void LobbyDatabase::insertPlayerIntoGameRoom(const std::string & accountID, const std::string & roomID) { insertGameRoomPlayersStatement->executeOnce(roomID, accountID); @@ -392,11 +380,10 @@ void LobbyDatabase::insertAccessCookie(const std::string & accountID, const std: insertAccessCookieStatement->executeOnce(accountID, accessCookieUUID); } -void LobbyDatabase::updateAccessCookie(const std::string & accountID, const std::string & accessCookieUUID) {} - -void LobbyDatabase::updateAccountLoginTime(const std::string & accountID) {} - -void LobbyDatabase::updateActiveAccount(const std::string & accountID, bool isActive) {} +void LobbyDatabase::updateAccountLoginTime(const std::string & accountID) +{ + assert(0); +} std::string LobbyDatabase::getAccountDisplayName(const std::string & accountID) { @@ -410,16 +397,16 @@ std::string LobbyDatabase::getAccountDisplayName(const std::string & accountID) return result; } -LobbyCookieStatus LobbyDatabase::getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime) -{ - return {}; -} +//LobbyCookieStatus LobbyDatabase::getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID) +//{ +// return {}; +//} -LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime) +LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID) { bool result = false; - isAccountCookieValidStatement->setBinds(accountID, accessCookieUUID, cookieLifetime.count()); + isAccountCookieValidStatement->setBinds(accountID, accessCookieUUID); if(isAccountCookieValidStatement->execute()) isAccountCookieValidStatement->getColumns(result); isAccountCookieValidStatement->reset(); @@ -429,6 +416,7 @@ LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & acco LobbyInviteStatus LobbyDatabase::getAccountInviteStatus(const std::string & accountID, const std::string & roomID) { + assert(0); return {}; } diff --git a/lobby/LobbyDatabase.h b/lobby/LobbyDatabase.h index 43caeae3e..8825b9063 100644 --- a/lobby/LobbyDatabase.h +++ b/lobby/LobbyDatabase.h @@ -62,7 +62,6 @@ public: void setAccountOnline(const std::string & accountID, bool isOnline); void setGameRoomStatus(const std::string & roomID, LobbyRoomState roomStatus); - void setGameRoomPlayerLimit(const std::string & roomID, uint32_t playerLimit); void insertPlayerIntoGameRoom(const std::string & accountID, const std::string & roomID); void deletePlayerFromGameRoom(const std::string & accountID, const std::string & roomID); @@ -75,21 +74,19 @@ public: void insertAccessCookie(const std::string & accountID, const std::string & accessCookieUUID); void insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomID, const std::string & messageText); - void updateAccessCookie(const std::string & accountID, const std::string & accessCookieUUID); void updateAccountLoginTime(const std::string & accountID); - void updateActiveAccount(const std::string & accountID, bool isActive); std::vector getActiveGameRooms(); std::vector getActiveAccounts(); - std::vector getAccountsInRoom(const std::string & roomID); +// std::vector getAccountsInRoom(const std::string & roomID); std::vector getRecentMessageHistory(); std::string getIdleGameRoom(const std::string & hostAccountID); std::string getAccountGameRoom(const std::string & accountID); std::string getAccountDisplayName(const std::string & accountID); - LobbyCookieStatus getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime); - LobbyCookieStatus getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime); +// LobbyCookieStatus getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID); + LobbyCookieStatus getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID); LobbyInviteStatus getAccountInviteStatus(const std::string & accountID, const std::string & roomID); LobbyRoomState getGameRoomStatus(const std::string & roomID); uint32_t getGameRoomFreeSlots(const std::string & roomID); diff --git a/lobby/LobbyDefines.h b/lobby/LobbyDefines.h index 710d9277d..b2b323f65 100644 --- a/lobby/LobbyDefines.h +++ b/lobby/LobbyDefines.h @@ -36,7 +36,6 @@ struct LobbyChatMessage enum class LobbyCookieStatus : int32_t { INVALID, - EXPIRED, VALID }; diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 551425025..e02f64366 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -17,8 +17,6 @@ #include #include -static const auto accountCookieLifetime = std::chrono::hours(24 * 7); - bool LobbyServer::isAccountNameValid(const std::string & accountName) const { if(accountName.size() < 4) @@ -60,7 +58,7 @@ NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) c void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json) { - networkServer->sendPacket(target, json.toBytes(true)); + target->sendPacket(json.toBytes(true)); } void LobbyServer::sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie) @@ -206,16 +204,27 @@ void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection) void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection, const std::string & errorMessage) { if(activeAccounts.count(connection)) + { database->setAccountOnline(activeAccounts.at(connection), false); + activeAccounts.erase(connection); + } if(activeGameRooms.count(connection)) + { database->setGameRoomStatus(activeGameRooms.at(connection), LobbyRoomState::CLOSED); + activeGameRooms.erase(connection); + } - // NOTE: lost connection can be in only one of these lists (or in none of them) - // calling on all possible containers since calling std::map::erase() with non-existing key is legal - activeAccounts.erase(connection); - activeProxies.erase(connection); - activeGameRooms.erase(connection); + if(activeProxies.count(connection)) + { + auto & otherConnection = activeProxies.at(connection); + + if (otherConnection) + otherConnection->close(); + + activeProxies.erase(connection); + activeProxies.erase(otherConnection); + } broadcastActiveAccounts(); broadcastActiveGameRooms(); @@ -226,7 +235,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons // proxy connection - no processing, only redirect if(activeProxies.count(connection)) { - auto lockedPtr = activeProxies.at(connection).lock(); + auto lockedPtr = activeProxies.at(connection); if(lockedPtr) return lockedPtr->sendPacket(message); @@ -296,9 +305,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons if(messageType == "serverProxyLogin") return receiveServerProxyLogin(connection, json); - // TODO: add logging of suspicious connections. - networkServer->closeConnection(connection); - + connection->close(); logGlobal->info("(unauthorised): Unknown message type %s", messageType); } @@ -348,13 +355,11 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co if(!database->isAccountIDExists(accountID)) return sendOperationFailed(connection, "Account not found"); - auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime); + auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie); if(clientCookieStatus == LobbyCookieStatus::INVALID) return sendOperationFailed(connection, "Authentification failure"); - // prolong existing cookie - database->updateAccessCookie(accountID, accountCookie); database->updateAccountLoginTime(accountID); database->setAccountOnline(accountID, true); @@ -365,8 +370,8 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co sendLoginSuccess(connection, accountCookie, displayName); sendChatHistory(connection, database->getRecentMessageHistory()); - // send active accounts list to new account - // and update acount list to everybody else + // send active game rooms list to new account + // and update acount list to everybody else including new account broadcastActiveAccounts(); sendMessage(connection, prepareActiveGameRooms()); } @@ -378,7 +383,7 @@ void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, co std::string accountCookie = json["accountCookie"].String(); std::string version = json["version"].String(); - auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime); + auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie); if(clientCookieStatus == LobbyCookieStatus::INVALID) { @@ -399,7 +404,7 @@ void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connectio std::string accountID = json["accountID"].String(); std::string accountCookie = json["accountCookie"].String(); - auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime); + auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie); if(clientCookieStatus != LobbyCookieStatus::INVALID) { @@ -424,7 +429,7 @@ void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connectio } sendOperationFailed(connection, "Invalid credentials"); - networkServer->closeConnection(connection); + connection->close(); } void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json) @@ -456,7 +461,7 @@ void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connectio return; } - //networkServer->closeConnection(connection); + //connection->close(); } void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json) @@ -480,9 +485,6 @@ void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, c if(roomType == "private") database->setGameRoomStatus(gameRoomID, LobbyRoomState::PRIVATE); - // TODO: additional flags / initial settings, e.g. allowCheats - // TODO: connection mode: direct or proxy. For now direct is assumed. Proxy might be needed later, for hosted servers - database->insertPlayerIntoGameRoom(accountID, gameRoomID); broadcastActiveGameRooms(); sendJoinRoomSuccess(connection, gameRoomID, false); diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index 6a3ebc5fa..e61b0111c 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -29,7 +29,7 @@ class LobbyServer final : public INetworkServerListener }; /// list of connected proxies. All messages received from (key) will be redirected to (value) connection - std::map activeProxies; + std::map activeProxies; /// list of half-established proxies from server that are still waiting for client to connect std::vector awaitingProxies; diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 6fe6ee669..9f170b33d 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -160,7 +160,7 @@ void CVCMIServer::onNewConnection(const std::shared_ptr & co } else { - networkServer->closeConnection(connection); + connection->close(); } } @@ -445,7 +445,7 @@ void CVCMIServer::clientConnected(std::shared_ptr c, std::vector c) { - networkServer->closeConnection(c->getConnection()); + c->getConnection()->close(); vstd::erase(activeConnections, c); if(activeConnections.empty() || hostClientId == c->connectionID) diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index 23a5037a7..3bafe24f5 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -29,7 +29,15 @@ void GlobalLobbyProcessor::establishNewConnection() void GlobalLobbyProcessor::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { - throw std::runtime_error("Lost connection to a lobby server!"); + if (connection == controlConnection) + { + throw std::runtime_error("Lost connection to a lobby server!"); + } + else + { + // player disconnected + owner.onDisconnected(connection, errorMessage); + } } void GlobalLobbyProcessor::onPacketReceived(const std::shared_ptr & connection, const std::vector & message) @@ -47,7 +55,7 @@ void GlobalLobbyProcessor::onPacketReceived(const std::shared_ptrerror("Received unexpected message from lobby server of type '%s' ", json["type"].String()); } else { From e6f0afd5865f194e97ade60431cf0005c630331d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 2 Feb 2024 15:50:33 +0100 Subject: [PATCH 58/98] Tweaking parameters, cleanup --- lib/rmg/CZonePlacer.cpp | 33 ++------------------------------- lib/rmg/CZonePlacer.h | 4 +--- lib/rmg/PenroseTiling.cpp | 20 +------------------- lib/rmg/PenroseTiling.h | 6 ++---- 4 files changed, 6 insertions(+), 57 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 383370ab5..6bcc27d64 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -27,7 +27,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CRandomGenerator; CZonePlacer::CZonePlacer(RmgMap & map) - : width(0), height(0), scaleX(0), scaleY(0), mapSize(0), + : width(0), height(0), mapSize(0), gravityConstant(1e-3f), stiffnessConstant(3e-3f), stifness(0), @@ -803,22 +803,6 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista float CZonePlacer::metric (const int3 &A, const int3 &B) const { - /* - float dx = abs(A.x - B.x) * scaleX; - float dy = abs(A.y - B.y) * scaleY; - - /* - 1. Normal euclidean distance - 2. Sinus for extra curves - 3. Nonlinear mess for fuzzy edges - */ - - /* - return dx * dx + dy * dy + - 5 * std::sin(dx * dy / 10) + - 25 * std::sin (std::sqrt(A.x * B.x) * (A.y - B.y) / 100 * (scaleX * scaleY)); - */ - return A.dist2dSQ(B); } @@ -830,9 +814,6 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) auto width = map.getMapGenOptions().getWidth(); auto height = map.getMapGenOptions().getHeight(); - //scale to Medium map to ensure smooth results - scaleX = 72.f / width; - scaleY = 72.f / height; auto zones = map.getZones(); vstd::erase_if(zones, [](const std::pair> & pr) @@ -903,7 +884,6 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) if(zone.second->area().empty()) throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size"); - // FIXME: Is 2. correct and doesn't break balance? moveZoneToCenterOfMass(zone.second); } @@ -912,7 +892,7 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) // Assign zones to closest Penrose vertex PenroseTiling penrose; - auto vertices = penrose.generatePenroseTiling(zones.size(), rand); + auto vertices = penrose.generatePenroseTiling(zones.size() / map.levels(), rand); std::map, std::set> vertexMapping; @@ -928,15 +908,6 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, closestZone->getPos().z)); //Closest vertex belongs to zone } - for (const auto & p : vertexMapping) - { - for (const auto vertex : p.second) - { - logGlobal->info("Zone %2d is assigned to vertex %s", p.first->getId(), vertex.toString()); - } - logGlobal->info("Zone %2d has total of %d vertices", p.first->getId(), p.second.size()); - } - //Assign actual tiles to each zone using nonlinear norm for fine edges for (pos.z = 0; pos.z < levels; pos.z++) { diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index 90b77b8f2..2eaf429ce 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -54,9 +54,7 @@ private: private: int width; int height; - //metric coeficients - float scaleX; - float scaleY; + //metric coeficient float mapSize; float gravityConstant; diff --git a/lib/rmg/PenroseTiling.cpp b/lib/rmg/PenroseTiling.cpp index 84a1404aa..e6ee0e9f4 100644 --- a/lib/rmg/PenroseTiling.cpp +++ b/lib/rmg/PenroseTiling.cpp @@ -124,7 +124,7 @@ void PenroseTiling::split(Triangle& p, std::vector& points, std::set PenroseTiling::generatePenroseTiling(size_t numZones, CRandomGenerator * rand) { - float scale = 100.f / (numZones + 20); + float scale = 100.f / (numZones * 1.5f + 20); float polyAngle = (2 * PI_CONSTANT) / POLY; float randomAngle = rand->nextDouble(0, 2 * PI_CONSTANT); @@ -143,14 +143,6 @@ std::set PenroseTiling::generatePenroseTiling(size_t numZones, CRandomG p.x(p.x() * scale * BASE_SIZE); } - // Scale square to window size - /* - for (auto& p : points) - { - p.x = (p.x / window_w) * window_h; - } - */ - std::set finalPoints; for (uint32_t i = 0; i < POLY; i++) @@ -162,21 +154,11 @@ std::set PenroseTiling::generatePenroseTiling(size_t numZones, CRandomG split(t, points, indices, DEPTH); } - /* - //No difference for the number of points - for (auto& p : points) - { - p = p + Point2D(0.5f, 0.5f); // Center in a square (0,1) - } - */ - vstd::copy_if(points, vstd::set_inserter(finalPoints), [](const Point2D point) { return vstd::isbetween(point.x(), 0.f, 1.0f) && vstd::isbetween(point.y(), 0.f, 1.0f); }); - logGlobal->info("Number of points within unit square: %d", finalPoints.size()); - return finalPoints; } diff --git a/lib/rmg/PenroseTiling.h b/lib/rmg/PenroseTiling.h index 3288f4dfb..bdda4c7c9 100644 --- a/lib/rmg/PenroseTiling.h +++ b/lib/rmg/PenroseTiling.h @@ -14,7 +14,6 @@ #include "../CRandomGenerator.h" #include #include -#include VCMI_LIB_NAMESPACE_BEGIN @@ -55,10 +54,9 @@ class PenroseTiling public: const float PHI = 1.0 / ((1.0 + std::sqrt(5.0)) / 2); - // TODO: Is that the number of symmetries? - const uint32_t POLY = 10; + const uint32_t POLY = 10; // Number of symmetries? - const float BASE_SIZE = 1.f; + const float BASE_SIZE = 1.25f; const uint32_t DEPTH = 7; //Recursion depth const bool P2 = false; // Tiling type From 6eef197ceadde502c0e5c7be31703d2c985cd251 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 3 Feb 2024 17:04:14 +0200 Subject: [PATCH 59/98] Removed no longer used mutexes from match server --- server/CGameHandler.cpp | 7 +------ server/CGameHandler.h | 1 - server/CVCMIServer.cpp | 10 ++++++---- server/CVCMIServer.h | 12 +++++------- server/GlobalLobbyProcessor.cpp | 2 +- server/TurnTimerHandler.cpp | 10 ---------- server/TurnTimerHandler.h | 1 - server/queries/CQuery.cpp | 2 -- server/queries/QueriesProcessor.cpp | 2 -- server/queries/QueriesProcessor.h | 2 -- 10 files changed, 13 insertions(+), 36 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index e192c62b9..4ea1e8029 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -983,10 +983,7 @@ void CGameHandler::start(bool resume) for (PlayerColor color : players) { sbuffer << color << " "; - { - boost::unique_lock lock(gsm); - connections[color].insert(cc); - } + connections[color].insert(cc); } logGlobal->info(sbuffer.str()); } @@ -3183,8 +3180,6 @@ bool CGameHandler::setFormation(ObjectInstanceID hid, EArmyFormation formation) bool CGameHandler::queryReply(QueryID qid, std::optional answer, PlayerColor player) { - boost::unique_lock lock(gsm); - logGlobal->trace("Player %s attempts answering query %d with answer:", player, qid); if (answer) logGlobal->trace("%d", *answer); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index dc81b0c92..934554123 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -73,7 +73,6 @@ public: std::map>> connections; //player color -> connection to client with interface of that player //queries stuff - boost::recursive_mutex gsm; ui32 QID; SpellCastEnvironment * spellEnv; diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 9f170b33d..8d55c85cd 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -247,7 +247,6 @@ void CVCMIServer::prepareToRestart() for(auto c : activeConnections) c->enterLobbyConnectionMode(); - boost::unique_lock queueLock(mx); gh = nullptr; } @@ -407,8 +406,7 @@ bool CVCMIServer::passHost(int toConnectionId) void CVCMIServer::clientConnected(std::shared_ptr c, std::vector & names, const std::string & uuid, EStartMode mode) { - if(state != EServerState::LOBBY) - throw std::runtime_error("CVCMIServer::clientConnected called while game is not accepting clients!"); + assert(state == EServerState::LOBBY); c->connectionID = currentClientId++; @@ -945,6 +943,10 @@ ui8 CVCMIServer::getIdOfFirstUnallocatedPlayer() const if(!si->getPlayersSettings(i->first)) return i->first; } - return 0; } + +INetworkHandler & CVCMIServer::getNetworkHandler() +{ + return *networkHandler; +} diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index bdee4e164..c53d21b98 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -57,16 +57,10 @@ class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INet std::chrono::steady_clock::time_point gameplayStartTime; std::chrono::steady_clock::time_point lastTimerUpdateTime; -public: - /// List of all active connections - std::vector> activeConnections; - std::unique_ptr networkHandler; -private: bool restartGameplay; // FIXME: this is just a hack - boost::recursive_mutex mx; std::shared_ptr> applier; EServerState state; @@ -76,6 +70,9 @@ private: ui8 currentPlayerId; public: + /// List of all active connections + std::vector> activeConnections; + // INetworkListener impl void onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) override; void onPacketReceived(const std::shared_ptr & connection, const std::vector & message) override; @@ -109,13 +106,14 @@ public: void clientDisconnected(std::shared_ptr c); void reconnectPlayer(int connId); -public: void announceMessage(const std::string & txt); void handleReceivedPack(std::unique_ptr pack); void updateAndPropagateLobbyState(); + INetworkHandler & getNetworkHandler(); + void setState(EServerState value); EServerState getState() const; diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index 3bafe24f5..b29142d25 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -24,7 +24,7 @@ void GlobalLobbyProcessor::establishNewConnection() { std::string hostname = settings["lobby"]["hostname"].String(); int16_t port = settings["lobby"]["port"].Integer(); - owner.networkHandler->connectToRemote(*this, hostname, port); + owner.getNetworkHandler().connectToRemote(*this, hostname, port); } void GlobalLobbyProcessor::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index 84a05c740..247ee4804 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -29,7 +29,6 @@ TurnTimerHandler::TurnTimerHandler(CGameHandler & gh): void TurnTimerHandler::onGameplayStart(PlayerColor player) { - std::lock_guard guard(mx); if(const auto * si = gameHandler.getStartInfo()) { timers[player] = si->turnTimerInfo; @@ -45,7 +44,6 @@ void TurnTimerHandler::onGameplayStart(PlayerColor player) void TurnTimerHandler::setTimerEnabled(PlayerColor player, bool enabled) { - std::lock_guard guard(mx); assert(player.isValidPlayer()); timers[player].isActive = enabled; sendTimerUpdate(player); @@ -53,7 +51,6 @@ void TurnTimerHandler::setTimerEnabled(PlayerColor player, bool enabled) void TurnTimerHandler::setEndTurnAllowed(PlayerColor player, bool enabled) { - std::lock_guard guard(mx); assert(player.isValidPlayer()); endTurnAllowed[player] = enabled; } @@ -69,7 +66,6 @@ void TurnTimerHandler::sendTimerUpdate(PlayerColor player) void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) { - std::lock_guard guard(mx); if(const auto * si = gameHandler.getStartInfo()) { if(si->turnTimerInfo.isEnabled()) @@ -87,7 +83,6 @@ void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) void TurnTimerHandler::update(int waitTime) { - std::lock_guard guard(mx); if(!gameHandler.getStartInfo()->turnTimerInfo.isEnabled()) return; @@ -122,7 +117,6 @@ bool TurnTimerHandler::timerCountDown(int & timer, int initialTimer, PlayerColor void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime) { - std::lock_guard guard(mx); const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs || !si->turnTimerInfo.isEnabled()) @@ -164,7 +158,6 @@ bool TurnTimerHandler::isPvpBattle(const BattleID & battleID) const void TurnTimerHandler::onBattleStart(const BattleID & battleID) { - std::lock_guard guard(mx); const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs) @@ -192,7 +185,6 @@ void TurnTimerHandler::onBattleStart(const BattleID & battleID) void TurnTimerHandler::onBattleEnd(const BattleID & battleID) { - std::lock_guard guard(mx); const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs) @@ -221,7 +213,6 @@ void TurnTimerHandler::onBattleEnd(const BattleID & battleID) void TurnTimerHandler::onBattleNextStack(const BattleID & battleID, const CStack & stack) { - std::lock_guard guard(mx); const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs || !gs->getBattle(battleID)) @@ -248,7 +239,6 @@ void TurnTimerHandler::onBattleNextStack(const BattleID & battleID, const CStack void TurnTimerHandler::onBattleLoop(const BattleID & battleID, int waitTime) { - std::lock_guard guard(mx); const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs) diff --git a/server/TurnTimerHandler.h b/server/TurnTimerHandler.h index c97eff721..8f6fae5a6 100644 --- a/server/TurnTimerHandler.h +++ b/server/TurnTimerHandler.h @@ -29,7 +29,6 @@ class TurnTimerHandler std::map timers; std::map lastUpdate; std::map endTurnAllowed; - std::recursive_mutex mx; void onPlayerMakingTurn(PlayerColor player, int waitTime); void onBattleLoop(const BattleID & battleID, int waitTime); diff --git a/server/queries/CQuery.cpp b/server/queries/CQuery.cpp index ad23dcc25..0580b75ed 100644 --- a/server/queries/CQuery.cpp +++ b/server/queries/CQuery.cpp @@ -49,8 +49,6 @@ CQuery::CQuery(CGameHandler * gameHandler) : owner(gameHandler->queries.get()) , gh(gameHandler) { - boost::unique_lock l(QueriesProcessor::mx); - static QueryID QID = QueryID(0); queryID = ++QID; diff --git a/server/queries/QueriesProcessor.cpp b/server/queries/QueriesProcessor.cpp index 259e33a3e..73918cbf4 100644 --- a/server/queries/QueriesProcessor.cpp +++ b/server/queries/QueriesProcessor.cpp @@ -12,8 +12,6 @@ #include "CQuery.h" -boost::mutex QueriesProcessor::mx; - void QueriesProcessor::popQuery(PlayerColor player, QueryPtr query) { LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query); diff --git a/server/queries/QueriesProcessor.h b/server/queries/QueriesProcessor.h index d0fd6df35..b46cd24fd 100644 --- a/server/queries/QueriesProcessor.h +++ b/server/queries/QueriesProcessor.h @@ -23,8 +23,6 @@ private: std::map> queries; //player => stack of queries public: - static boost::mutex mx; - void addQuery(QueryPtr query); void popQuery(const CQuery &query); void popQuery(QueryPtr query); From c66de2955001c202600957471492a2105960ba5e Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 3 Feb 2024 17:46:03 +0200 Subject: [PATCH 60/98] #3550 - fix case when unit is in reachability map but only next turn --- AI/BattleAI/BattleExchangeVariant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 9846a6efe..f0a29682b 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -843,7 +843,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb } } - if(!reachable && vstd::contains(reachabilityMap[hex], unit)) + if(!reachable && std::count(reachabilityMap[hex].begin(), reachabilityMap[hex].end(), unit) > 1) { blockingScore += ratio * (enemyUnit ? BLOCKING_OWN_ATTACK_PENALTY : BLOCKING_OWN_MOVE_PENALTY); } From 2c2bec791cb7e33d46f80f45fe9bb8bab619a2e4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 3 Feb 2024 19:08:45 +0200 Subject: [PATCH 61/98] Fixes and cleanup of game client network shutdown and restart --- client/CServerHandler.cpp | 108 ++++++++------------ client/CServerHandler.h | 10 +- client/LobbyClientNetPackVisitors.h | 2 +- client/NetPacksLobbyClient.cpp | 11 +- lib/network/NetworkConnection.h | 2 +- lib/networkPacks/NetPackVisitor.h | 2 +- lib/networkPacks/NetPacksLib.cpp | 4 +- lib/networkPacks/PacksForLobby.h | 7 +- lib/registerTypes/RegisterTypesLobbyPacks.h | 2 +- lib/serializer/Connection.cpp | 1 + server/CGameHandler.cpp | 5 +- server/CVCMIServer.cpp | 56 +++++----- server/CVCMIServer.h | 6 +- server/GlobalLobbyProcessor.cpp | 7 +- server/LobbyNetPackVisitors.h | 6 +- server/NetPacksLobbyServer.cpp | 17 +-- server/processors/TurnOrderProcessor.cpp | 2 +- 17 files changed, 102 insertions(+), 146 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 0516cc721..e5caabb37 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -131,12 +131,12 @@ CServerHandler::~CServerHandler() networkHandler->stop(); try { - threadNetwork->join(); + threadNetwork.join(); } catch (const std::runtime_error & e) { logGlobal->error("Failed to shut down network thread! Reason: %s", e.what()); - assert(false); + assert(0); } } @@ -144,17 +144,16 @@ CServerHandler::CServerHandler() : applier(std::make_unique>()) , lobbyClient(std::make_unique()) , networkHandler(INetworkHandler::createHandler()) + , threadNetwork(&CServerHandler::threadRunNetwork, this) , state(EClientState::NONE) , campaignStateToSend(nullptr) , screenType(ESelectionScreen::unknown) , serverMode(EServerMode::NONE) , loadMode(ELoadMode::NONE) , client(nullptr) - , campaignServerRestartLock(false) { uuid = boost::uuids::to_string(boost::uuids::random_generator()()); registerTypesLobbyPacks(*applier); - threadNetwork = std::make_unique(&CServerHandler::threadRunNetwork, this); } void CServerHandler::threadRunNetwork() @@ -192,8 +191,8 @@ GlobalLobbyClient & CServerHandler::getGlobalLobby() void CServerHandler::startLocalServerAndConnect(bool connectToLobby) { - if(threadRunLocalServer) - threadRunLocalServer->join(); + if(threadRunLocalServer.joinable()) + threadRunLocalServer.join(); th->update(); @@ -203,19 +202,17 @@ void CServerHandler::startLocalServerAndConnect(bool connectToLobby) if(connectToLobby) args.push_back("--lobby"); - threadRunLocalServer = std::make_unique([&cond, args, this] { + threadRunLocalServer = boost::thread([&cond, args] { setThreadName("CVCMIServer"); CVCMIServer::create(&cond, args); - onServerFinished(); }); - threadRunLocalServer->detach(); #elif defined(VCMI_ANDROID) { CAndroidVMHelper envHelper; envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "startServer", true); } #else - threadRunLocalServer = std::make_unique(&CServerHandler::threadRunServer, this, connectToLobby); //runs server executable; + threadRunLocalServer = boost::thread(&CServerHandler::threadRunServer, this, connectToLobby); //runs server executable; #endif logNetwork->trace("Setting up thread calling server: %d ms", th->getDiff()); @@ -357,10 +354,7 @@ ui8 CServerHandler::myFirstId() const bool CServerHandler::isServerLocal() const { - if(threadRunLocalServer) - return true; - - return false; + return threadRunLocalServer.joinable(); } bool CServerHandler::isHost() const @@ -416,7 +410,10 @@ void CServerHandler::sendClientDisconnecting() { // FIXME: This is workaround needed to make sure client not trying to sent anything to non existed server if(state == EClientState::DISCONNECTING) + { + assert(0); return; + } state = EClientState::DISCONNECTING; mapToStart = nullptr; @@ -433,12 +430,8 @@ void CServerHandler::sendClientDisconnecting() logNetwork->info("Sent leaving signal to the server"); } sendLobbyPack(lcd); - - { - // Network thread might be applying network pack at this moment - auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); - c.reset(); - } + c->getConnection()->close(); + c.reset(); } void CServerHandler::setCampaignState(std::shared_ptr newCampaign) @@ -585,9 +578,7 @@ void CServerHandler::sendRestartGame() const { GH.windows().createAndPushWindow(); - LobbyEndGame endGame; - endGame.closeConnection = false; - endGame.restart = true; + LobbyRestartGame endGame; sendLobbyPack(endGame); } @@ -676,40 +667,37 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta state = EClientState::GAMEPLAY; } -void CServerHandler::endGameplay(bool closeConnection, bool restart) +void CServerHandler::endGameplay() { - if(closeConnection) - { - // Game is ending - // Tell the network thread to reach a stable state - CSH->sendClientDisconnecting(); - logNetwork->info("Closed connection."); - } + // Game is ending + // Tell the network thread to reach a stable state + CSH->sendClientDisconnecting(); + logNetwork->info("Closed connection."); client->endGame(); client.reset(); - if(!restart) + if(CMM) { - if(CMM) - { - GH.curInt = CMM.get(); - CMM->enable(); - } - else - { - GH.curInt = CMainMenu::create().get(); - } + GH.curInt = CMM.get(); + CMM->enable(); } - - if(c) + else { - nextClient = std::make_unique(); - c->setCallback(nextClient.get()); - c->enterLobbyConnectionMode(); + GH.curInt = CMainMenu::create().get(); } } +void CServerHandler::restartGameplay() +{ + client->endGame(); + client.reset(); + + nextClient = std::make_unique(); + c->setCallback(nextClient.get()); + c->enterLobbyConnectionMode(); +} + void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared_ptr cs) { std::shared_ptr ourCampaign = cs; @@ -728,7 +716,6 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared GH.dispatchMainThread([ourCampaign, this]() { - CSH->campaignServerRestartLock.set(true); CSH->endGameplay(); auto & epilogue = ourCampaign->scenario(*ourCampaign->lastScenario()).epilog; @@ -751,13 +738,14 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared GH.windows().createAndPushWindow(true, *highScoreCalc); } }; + + threadRunLocalServer.join(); if(epilogue.hasPrologEpilog) { GH.windows().createAndPushWindow(epilogue, finisher); } else { - CSH->campaignServerRestartLock.waitUntil(false); finisher(); } }); @@ -877,23 +865,19 @@ public: void CServerHandler::onPacketReceived(const std::shared_ptr &, const std::vector & message) { - CPack * pack = c->retrievePack(message); if(state == EClientState::DISCONNECTING) { - // FIXME: server shouldn't really send netpacks after it's tells client to disconnect - // Though currently they'll be delivered and might cause crash. - vstd::clear_pointer(pack); - } - else - { - ServerHandlerCPackVisitor visitor(*this); - pack->visit(visitor); + assert(0); //Should not be possible - socket must be closed at this point + return; } + + CPack * pack = c->retrievePack(message); + ServerHandlerCPackVisitor visitor(*this); + pack->visit(visitor); } void CServerHandler::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { - assert(networkConnection == connection); networkConnection.reset(); if(state == EClientState::DISCONNECTING) @@ -987,17 +971,9 @@ void CServerHandler::threadRunServer(bool connectToLobby) logNetwork->error("Error: server failed to close correctly or crashed!"); logNetwork->error("Check %s for more info", logName); } - onServerFinished(); #endif } -void CServerHandler::onServerFinished() -{ - threadRunLocalServer.reset(); - if (CSH) - CSH->campaignServerRestartLock.setn(false); -} - void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const { if(state != EClientState::STARTING) diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 657747e24..7e44cbfe6 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -111,7 +111,6 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor /// required to correctly deserialize gamestate using client-side game callback std::unique_ptr nextClient; - void onServerFinished(); void sendLobbyPack(const CPackForLobby & pack) const override; void onPacketReceived(const NetworkConnectionPtr &, const std::vector & message) override; @@ -145,13 +144,11 @@ public: //////////////////// std::unique_ptr th; - std::unique_ptr threadRunLocalServer; - std::unique_ptr threadNetwork; + boost::thread threadRunLocalServer; + boost::thread threadNetwork; std::unique_ptr client; - CondSh campaignServerRestartLock; - CServerHandler(); ~CServerHandler(); @@ -202,7 +199,8 @@ public: void debugStartTest(std::string filename, bool save = false); void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr); - void endGameplay(bool closeConnection = true, bool restart = false); + void endGameplay(); + void restartGameplay(); void startCampaignScenario(HighScoreParameter param, std::shared_ptr cs = {}); void showServerError(const std::string & txt) const; diff --git a/client/LobbyClientNetPackVisitors.h b/client/LobbyClientNetPackVisitors.h index 7e19e614b..b479c9ba1 100644 --- a/client/LobbyClientNetPackVisitors.h +++ b/client/LobbyClientNetPackVisitors.h @@ -34,7 +34,7 @@ public: virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbyEndGame(LobbyEndGame & pack) override; + virtual void visitLobbyRestartGame(LobbyRestartGame & pack) override; virtual void visitLobbyStartGame(LobbyStartGame & pack) override; virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override; }; diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index eea298089..196c5718a 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -133,18 +133,15 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack } } -void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack) +void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) { if(handler.state == EClientState::GAMEPLAY) { - handler.endGameplay(pack.closeConnection, pack.restart); + handler.restartGameplay(); } - if(pack.restart) - { - if (handler.validateGameStart()) - handler.sendStartGame(); - } + if (handler.validateGameStart()) + handler.sendStartGame(); } void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h index e445329d1..4f7010ea1 100644 --- a/lib/network/NetworkConnection.h +++ b/lib/network/NetworkConnection.h @@ -13,7 +13,7 @@ VCMI_LIB_NAMESPACE_BEGIN -class NetworkConnection : public INetworkConnection, std::enable_shared_from_this +class NetworkConnection : public INetworkConnection, public std::enable_shared_from_this { static const int messageHeaderSize = sizeof(uint32_t); static const int messageMaxSize = 64 * 1024 * 1024; // arbitrary size to prevent potential massive allocation if we receive garbage input diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index c77705174..46ed984dd 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -151,7 +151,7 @@ public: virtual void visitLobbyChatMessage(LobbyChatMessage & pack) {} virtual void visitLobbyGuiAction(LobbyGuiAction & pack) {} virtual void visitLobbyLoadProgress(LobbyLoadProgress & pack) {} - virtual void visitLobbyEndGame(LobbyEndGame & pack) {} + virtual void visitLobbyRestartGame(LobbyRestartGame & pack) {} virtual void visitLobbyStartGame(LobbyStartGame & pack) {} virtual void visitLobbyChangeHost(LobbyChangeHost & pack) {} virtual void visitLobbyUpdateState(LobbyUpdateState & pack) {} diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index bbe706877..2ab861ea8 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -708,9 +708,9 @@ void LobbyLoadProgress::visitTyped(ICPackVisitor & visitor) visitor.visitLobbyLoadProgress(*this); } -void LobbyEndGame::visitTyped(ICPackVisitor & visitor) +void LobbyRestartGame::visitTyped(ICPackVisitor & visitor) { - visitor.visitLobbyEndGame(*this); + visitor.visitLobbyRestartGame(*this); } void LobbyStartGame::visitTyped(ICPackVisitor & visitor) diff --git a/lib/networkPacks/PacksForLobby.h b/lib/networkPacks/PacksForLobby.h index a5df08f0b..34c920f55 100644 --- a/lib/networkPacks/PacksForLobby.h +++ b/lib/networkPacks/PacksForLobby.h @@ -111,17 +111,12 @@ struct DLL_LINKAGE LobbyLoadProgress : public CLobbyPackToPropagate } }; -struct DLL_LINKAGE LobbyEndGame : public CLobbyPackToPropagate +struct DLL_LINKAGE LobbyRestartGame : public CLobbyPackToPropagate { - bool closeConnection = false; - bool restart = false; - void visitTyped(ICPackVisitor & visitor) override; template void serialize(Handler &h) { - h & closeConnection; - h & restart; } }; diff --git a/lib/registerTypes/RegisterTypesLobbyPacks.h b/lib/registerTypes/RegisterTypesLobbyPacks.h index 8ef908b34..c11687dc6 100644 --- a/lib/registerTypes/RegisterTypesLobbyPacks.h +++ b/lib/registerTypes/RegisterTypesLobbyPacks.h @@ -37,7 +37,7 @@ void registerTypesLobbyPacks(Serializer &s) // Only host client send s.template registerType(); s.template registerType(); - s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); // Only server send diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index cc6c83719..8e9bdd9f2 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -140,6 +140,7 @@ void CConnection::enterGameplayConnectionMode(CGameState * gs) setCallback(gs->callback); packWriter->addStdVecItems(gs); + packReader->addStdVecItems(gs); } void CConnection::disableSmartPointerSerialization() diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4ea1e8029..4d5ee16c2 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -445,7 +445,10 @@ void CGameHandler::changeSecSkill(const CGHeroInstance * hero, SecondarySkill wh void CGameHandler::handleClientDisconnection(std::shared_ptr c) { if(lobby->getState() == EServerState::SHUTDOWN || !gs || !gs->scenarioOps) + { + assert(0); // game should have shut down before reaching this point! return; + } for(auto & playerConnections : connections) { @@ -3609,7 +3612,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) if(p->human) { - lobby->setState(EServerState::GAMEPLAY_ENDED); + lobby->setState(EServerState::SHUTDOWN); } } else diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 8d55c85cd..6e11692a9 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -117,9 +117,7 @@ public: }; CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts) - : restartGameplay(false) - , state(EServerState::LOBBY) - , currentClientId(1) + : currentClientId(1) , currentPlayerId(1) , cmdLineOptions(opts) { @@ -153,13 +151,14 @@ void CVCMIServer::startAcceptingIncomingConnections() void CVCMIServer::onNewConnection(const std::shared_ptr & connection) { - if(state == EServerState::LOBBY) + if(getState() == EServerState::LOBBY) { activeConnections.push_back(std::make_shared(connection)); activeConnections.back()->enterLobbyConnectionMode(); } else { + // TODO: reconnection support connection->close(); } } @@ -175,7 +174,11 @@ void CVCMIServer::onPacketReceived(const std::shared_ptr & c void CVCMIServer::setState(EServerState value) { + assert(state != EServerState::SHUTDOWN); // do not attempt to restart dying server state = value; + + if (state == EServerState::SHUTDOWN) + networkHandler->stop(); } EServerState CVCMIServer::getState() const @@ -208,7 +211,8 @@ void CVCMIServer::run() void CVCMIServer::onTimer() { - if (state != EServerState::GAMEPLAY) + // we might receive onTimer call after transitioning from GAMEPLAY to LOBBY state, e.g. on game restart + if (getState() != EServerState::GAMEPLAY) return; static const auto serverUpdateInterval = std::chrono::milliseconds(100); @@ -230,18 +234,20 @@ void CVCMIServer::onTimer() void CVCMIServer::prepareToRestart() { - if(state == EServerState::GAMEPLAY) + if(getState() != EServerState::GAMEPLAY) { - restartGameplay = true; - * si = * gh->gs->initialOpts; - si->seedToBeUsed = si->seedPostInit = 0; - state = EServerState::LOBBY; - if (si->campState) - { - assert(si->campState->currentScenario().has_value()); - campaignMap = si->campState->currentScenario().value_or(CampaignScenarioID(0)); - campaignBonus = si->campState->getBonusID(campaignMap).value_or(-1); - } + assert(0); + return; + } + + * si = * gh->gs->initialOpts; + si->seedToBeUsed = si->seedPostInit = 0; + setState(EServerState::LOBBY); + if (si->campState) + { + assert(si->campState->currentScenario().has_value()); + campaignMap = si->campState->currentScenario().value_or(CampaignScenarioID(0)); + campaignBonus = si->campState->getBonusID(campaignMap).value_or(-1); } for(auto c : activeConnections) @@ -319,7 +325,7 @@ void CVCMIServer::startGameImmediately() c->enterGameplayConnectionMode(gh->gs); gh->start(si->mode == EStartMode::LOAD_GAME); - state = EServerState::GAMEPLAY; + setState(EServerState::GAMEPLAY); lastTimerUpdateTime = gameplayStartTime = std::chrono::steady_clock::now(); onTimer(); } @@ -333,12 +339,11 @@ void CVCMIServer::onDisconnected(const std::shared_ptr & con if(activeConnections.empty() || hostClientId == c->connectionID) { - networkHandler->stop(); - state = EServerState::SHUTDOWN; + setState(EServerState::SHUTDOWN); return; } - if(gh && state == EServerState::GAMEPLAY) + if(gh && getState() == EServerState::GAMEPLAY) { gh->handleClientDisconnection(c); @@ -406,7 +411,7 @@ bool CVCMIServer::passHost(int toConnectionId) void CVCMIServer::clientConnected(std::shared_ptr c, std::vector & names, const std::string & uuid, EStartMode mode) { - assert(state == EServerState::LOBBY); + assert(getState() == EServerState::LOBBY); c->connectionID = currentClientId++; @@ -446,13 +451,6 @@ void CVCMIServer::clientDisconnected(std::shared_ptr c) c->getConnection()->close(); vstd::erase(activeConnections, c); - if(activeConnections.empty() || hostClientId == c->connectionID) - { - networkHandler->stop(); - state = EServerState::SHUTDOWN; - return; - } - // PlayerReinitInterface startAiPack; // startAiPack.playerConnectionId = PlayerSettings::PLAYER_AI; // @@ -494,7 +492,7 @@ void CVCMIServer::reconnectPlayer(int connId) PlayerReinitInterface startAiPack; startAiPack.playerConnectionId = connId; - if(gh && si && state == EServerState::GAMEPLAY) + if(gh && si && getState() == EServerState::GAMEPLAY) { for(auto it = playerNames.begin(); it != playerNames.end(); ++it) { diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index c53d21b98..63da18f76 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -42,9 +42,7 @@ class GlobalLobbyProcessor; enum class EServerState : ui8 { LOBBY, - GAMEPLAY_STARTING, GAMEPLAY, - GAMEPLAY_ENDED, SHUTDOWN }; @@ -59,10 +57,8 @@ class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INet std::unique_ptr networkHandler; - bool restartGameplay; // FIXME: this is just a hack - std::shared_ptr> applier; - EServerState state; + EServerState state = EServerState::LOBBY; std::shared_ptr findConnection(const std::shared_ptr &); diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index b29142d25..5b0bae7d6 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -31,7 +31,8 @@ void GlobalLobbyProcessor::onDisconnected(const std::shared_ptrinfo("Lobby: Failed to login into a lobby server!"); - throw std::runtime_error("Failed to login into a lobby server!"); + owner.setState(EServerState::SHUTDOWN); } void GlobalLobbyProcessor::receiveLoginSuccess(const JsonNode & json) @@ -91,7 +92,7 @@ void GlobalLobbyProcessor::receiveAccountJoinsRoom(const JsonNode & json) void GlobalLobbyProcessor::onConnectionFailed(const std::string & errorMessage) { - throw std::runtime_error("Failed to connect to a lobby server!"); + owner.setState(EServerState::SHUTDOWN); } void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr & connection) diff --git a/server/LobbyNetPackVisitors.h b/server/LobbyNetPackVisitors.h index 6304f561a..84be9581e 100644 --- a/server/LobbyNetPackVisitors.h +++ b/server/LobbyNetPackVisitors.h @@ -31,7 +31,7 @@ public: virtual void visitForLobby(CPackForLobby & pack) override; virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbyEndGame(LobbyEndGame & pack) override; + virtual void visitLobbyRestartGame(LobbyRestartGame & pack) override; virtual void visitLobbyStartGame(LobbyStartGame & pack) override; virtual void visitLobbyChangeHost(LobbyChangeHost & pack) override; virtual void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override; @@ -53,7 +53,7 @@ public: virtual void visitForLobby(CPackForLobby & pack) override; virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbyEndGame(LobbyEndGame & pack) override; + virtual void visitLobbyRestartGame(LobbyRestartGame & pack) override; virtual void visitLobbyStartGame(LobbyStartGame & pack) override; virtual void visitLobbyChangeHost(LobbyChangeHost & pack) override; }; @@ -81,7 +81,7 @@ public: virtual void visitLobbySetCampaign(LobbySetCampaign & pack) override; virtual void visitLobbySetCampaignMap(LobbySetCampaignMap & pack) override; virtual void visitLobbySetCampaignBonus(LobbySetCampaignBonus & pack) override; - virtual void visitLobbyEndGame(LobbyEndGame & pack) override; + virtual void visitLobbyRestartGame(LobbyRestartGame & pack) override; virtual void visitLobbyStartGame(LobbyStartGame & pack) override; virtual void visitLobbyChangeHost(LobbyChangeHost & pack) override; virtual void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override; diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index 228e948a0..96d49f29f 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -36,14 +36,7 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitForLobby(CPackForLobby & pac void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack) { - if(srv.getState() == EServerState::LOBBY) - { - result = true; - return; - } - - result = false; - return; + result = srv.getState() == EServerState::LOBBY; } void ApplyOnServerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack) @@ -197,19 +190,19 @@ void ClientPermissionsCheckerNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction result = srv.isClientHost(pack.c->connectionID); } -void ClientPermissionsCheckerNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack) +void ClientPermissionsCheckerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) { result = srv.isClientHost(pack.c->connectionID); } -void ApplyOnServerNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack) +void ApplyOnServerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) { srv.prepareToRestart(); result = true; } -void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack) +void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) { for(auto & c : srv.activeConnections) c->enterLobbyConnectionMode(); @@ -241,8 +234,6 @@ void ApplyOnServerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) pack.initializedStartInfo = std::make_shared(*srv.gh->getStartInfo(true)); pack.initializedGameState = srv.gh->gameState(); - - srv.setState(EServerState::GAMEPLAY_STARTING); result = true; } diff --git a/server/processors/TurnOrderProcessor.cpp b/server/processors/TurnOrderProcessor.cpp index 0d762794e..2266036a5 100644 --- a/server/processors/TurnOrderProcessor.cpp +++ b/server/processors/TurnOrderProcessor.cpp @@ -233,7 +233,7 @@ void TurnOrderProcessor::doStartNewDay() if(!activePlayer) { - gameHandler->gameLobby()->setState(EServerState::GAMEPLAY_ENDED); + gameHandler->gameLobby()->setState(EServerState::SHUTDOWN); return; } From bd4c7e3ac0999a4835f71c3549d4bd87b904992c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 3 Feb 2024 19:57:23 +0200 Subject: [PATCH 62/98] Added LobbyPrepareStartGame pack to replace old workarounds --- client/CServerHandler.cpp | 10 +++------- client/CServerHandler.h | 4 ---- client/LobbyClientNetPackVisitors.h | 1 + client/NetPacksLobbyClient.cpp | 8 ++++++++ lib/networkPacks/NetPackVisitor.h | 1 + lib/networkPacks/NetPacksLib.cpp | 5 +++++ lib/networkPacks/PacksForLobby.h | 9 +++++++++ lib/registerTypes/RegisterTypesLobbyPacks.h | 1 + lib/serializer/Connection.cpp | 12 +++++------- lib/serializer/Connection.h | 2 +- server/LobbyNetPackVisitors.h | 1 + server/NetPacksLobbyServer.cpp | 5 +++++ 12 files changed, 40 insertions(+), 19 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index e5caabb37..4b65bc51d 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -317,10 +317,8 @@ void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netCon } c = std::make_shared(netConnection); - nextClient = std::make_unique(); c->uuid = uuid; c->enterLobbyConnectionMode(); - c->setCallback(nextClient.get()); sendClientConnecting(); } @@ -622,6 +620,9 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const if(!settings["session"]["headless"].Bool()) GH.windows().createAndPushWindow(); + LobbyPrepareStartGame lpsg; + sendLobbyPack(lpsg); + LobbyStartGame lsg; if(client) { @@ -631,7 +632,6 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const * si = * lsg.initializedStartInfo; } sendLobbyPack(lsg); - c->enterLobbyConnectionMode(); } void CServerHandler::startMapAfterConnection(std::shared_ptr to) @@ -644,8 +644,6 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta if(CMM) CMM->disable(); - std::swap(client, nextClient); - highScoreCalc = nullptr; switch(si->mode) @@ -693,8 +691,6 @@ void CServerHandler::restartGameplay() client->endGame(); client.reset(); - nextClient = std::make_unique(); - c->setCallback(nextClient.get()); c->enterLobbyConnectionMode(); } diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 7e44cbfe6..22d8d9535 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -107,10 +107,6 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor void threadRunNetwork(); void threadRunServer(bool connectToLobby); - /// temporary helper member that exists while game in lobby mode - /// required to correctly deserialize gamestate using client-side game callback - std::unique_ptr nextClient; - void sendLobbyPack(const CPackForLobby & pack) const override; void onPacketReceived(const NetworkConnectionPtr &, const std::vector & message) override; diff --git a/client/LobbyClientNetPackVisitors.h b/client/LobbyClientNetPackVisitors.h index b479c9ba1..041e06a4b 100644 --- a/client/LobbyClientNetPackVisitors.h +++ b/client/LobbyClientNetPackVisitors.h @@ -35,6 +35,7 @@ public: virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; virtual void visitLobbyRestartGame(LobbyRestartGame & pack) override; + virtual void visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) override; virtual void visitLobbyStartGame(LobbyStartGame & pack) override; virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override; }; diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index 196c5718a..9165df5d2 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -25,6 +25,7 @@ #include "CServerHandler.h" #include "CGameInfo.h" +#include "Client.h" #include "gui/CGuiHandler.h" #include "gui/WindowHandler.h" #include "widgets/Buttons.h" @@ -144,6 +145,13 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & handler.sendStartGame(); } +void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) +{ + handler.client = std::make_unique(); + handler.c->enterLobbyConnectionMode(); + handler.c->setCallback(handler.client.get()); +} + void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) { if(pack.clientId != -1 && pack.clientId != handler.c->connectionID) diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index 46ed984dd..fe1d9181e 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -152,6 +152,7 @@ public: virtual void visitLobbyGuiAction(LobbyGuiAction & pack) {} virtual void visitLobbyLoadProgress(LobbyLoadProgress & pack) {} virtual void visitLobbyRestartGame(LobbyRestartGame & pack) {} + virtual void visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) {} virtual void visitLobbyStartGame(LobbyStartGame & pack) {} virtual void visitLobbyChangeHost(LobbyChangeHost & pack) {} virtual void visitLobbyUpdateState(LobbyUpdateState & pack) {} diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 2ab861ea8..7eea2dde5 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -718,6 +718,11 @@ void LobbyStartGame::visitTyped(ICPackVisitor & visitor) visitor.visitLobbyStartGame(*this); } +void LobbyPrepareStartGame::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitLobbyPrepareStartGame(*this); +} + void LobbyChangeHost::visitTyped(ICPackVisitor & visitor) { visitor.visitLobbyChangeHost(*this); diff --git a/lib/networkPacks/PacksForLobby.h b/lib/networkPacks/PacksForLobby.h index 34c920f55..8fc5c055a 100644 --- a/lib/networkPacks/PacksForLobby.h +++ b/lib/networkPacks/PacksForLobby.h @@ -120,6 +120,15 @@ struct DLL_LINKAGE LobbyRestartGame : public CLobbyPackToPropagate } }; +struct DLL_LINKAGE LobbyPrepareStartGame : public CLobbyPackToPropagate +{ + void visitTyped(ICPackVisitor & visitor) override; + + template void serialize(Handler &h) + { + } +}; + struct DLL_LINKAGE LobbyStartGame : public CLobbyPackToPropagate { // Set by server diff --git a/lib/registerTypes/RegisterTypesLobbyPacks.h b/lib/registerTypes/RegisterTypesLobbyPacks.h index c11687dc6..3eade0382 100644 --- a/lib/registerTypes/RegisterTypesLobbyPacks.h +++ b/lib/registerTypes/RegisterTypesLobbyPacks.h @@ -38,6 +38,7 @@ void registerTypesLobbyPacks(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); // Only server send diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index 8e9bdd9f2..c72921d2f 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -62,8 +62,7 @@ CConnection::CConnection(std::weak_ptr networkConnection) { assert(networkConnection.lock() != nullptr); - enableSmartPointerSerialization(); - disableStackSendingByID(); + enterLobbyConnectionMode(); deserializer->version = ESerializationVersion::CURRENT; } @@ -139,8 +138,7 @@ void CConnection::enterGameplayConnectionMode(CGameState * gs) disableSmartPointerSerialization(); setCallback(gs->callback); - packWriter->addStdVecItems(gs); - packReader->addStdVecItems(gs); + enableSmartVectorMemberSerializatoin(gs); } void CConnection::disableSmartPointerSerialization() @@ -161,10 +159,10 @@ void CConnection::disableSmartVectorMemberSerialization() packWriter->smartVectorMembersSerialization = false; } -void CConnection::enableSmartVectorMemberSerializatoin() +void CConnection::enableSmartVectorMemberSerializatoin(CGameState * gs) { - packReader->smartVectorMembersSerialization = true; - packWriter->smartVectorMembersSerialization = true; + packWriter->addStdVecItems(gs); + packReader->addStdVecItems(gs); } VCMI_LIB_NAMESPACE_END diff --git a/lib/serializer/Connection.h b/lib/serializer/Connection.h index 5e18bbbf7..3d14f261b 100644 --- a/lib/serializer/Connection.h +++ b/lib/serializer/Connection.h @@ -37,7 +37,7 @@ class DLL_LINKAGE CConnection : boost::noncopyable void disableSmartPointerSerialization(); void enableSmartPointerSerialization(); void disableSmartVectorMemberSerialization(); - void enableSmartVectorMemberSerializatoin(); + void enableSmartVectorMemberSerializatoin(CGameState * gs); public: bool isMyConnection(const std::shared_ptr & otherConnection) const; diff --git a/server/LobbyNetPackVisitors.h b/server/LobbyNetPackVisitors.h index 84be9581e..3e3d2f1e3 100644 --- a/server/LobbyNetPackVisitors.h +++ b/server/LobbyNetPackVisitors.h @@ -32,6 +32,7 @@ public: virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; virtual void visitLobbyRestartGame(LobbyRestartGame & pack) override; + virtual void visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) override; virtual void visitLobbyStartGame(LobbyStartGame & pack) override; virtual void visitLobbyChangeHost(LobbyChangeHost & pack) override; virtual void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override; diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index 96d49f29f..e58c4af99 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -208,6 +208,11 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyRestartGame(LobbyRestar c->enterLobbyConnectionMode(); } +void ClientPermissionsCheckerNetPackVisitor::visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) +{ + result = srv.isClientHost(pack.c->connectionID); +} + void ClientPermissionsCheckerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) { result = srv.isClientHost(pack.c->connectionID); From 1b6ac1052a9367ddff6631ddad992091839ff7ba Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 3 Feb 2024 22:24:32 +0200 Subject: [PATCH 63/98] Properly lock UI mutex on accessing GUI state from network thread --- client/CServerHandler.cpp | 62 ++++++++++++++---------- client/Client.cpp | 1 - client/globalLobby/GlobalLobbyClient.cpp | 6 +++ server/CVCMIServer.cpp | 7 +-- 4 files changed, 46 insertions(+), 30 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 4b65bc51d..dbb5d01c4 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -88,8 +88,6 @@ template class CApplyOnLobby : public CBaseForLobbyApply public: bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override { - boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); - auto & ref = static_cast(pack); ApplyOnLobbyHandlerNetPackVisitor visitor(*handler); @@ -278,6 +276,9 @@ void CServerHandler::connectToServer(const std::string & addr, const ui16 port) void CServerHandler::onConnectionFailed(const std::string & errorMessage) { + assert(state == EClientState::CONNECTING); + boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); + if (isServerLocal()) { // retry - local server might be still starting up @@ -294,6 +295,8 @@ void CServerHandler::onConnectionFailed(const std::string & errorMessage) void CServerHandler::onTimer() { + boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); + if(state == EClientState::CONNECTION_CANCELLED) { logNetwork->info("Connection aborted by player!"); @@ -306,6 +309,10 @@ void CServerHandler::onTimer() void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netConnection) { + assert(state == EClientState::CONNECTING); + + boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); + networkConnection = netConnection; logNetwork->info("Connection established"); @@ -324,7 +331,6 @@ void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netCon void CServerHandler::applyPackOnLobbyScreen(CPackForLobby & pack) { - boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); const CBaseForLobbyApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(&pack)); //find the applier apply->applyOnLobbyScreen(dynamic_cast(SEL), this, pack); GH.windows().totalRedraw(); @@ -428,7 +434,7 @@ void CServerHandler::sendClientDisconnecting() logNetwork->info("Sent leaving signal to the server"); } sendLobbyPack(lcd); - c->getConnection()->close(); + networkConnection.reset(); c.reset(); } @@ -861,6 +867,8 @@ public: void CServerHandler::onPacketReceived(const std::shared_ptr &, const std::vector & message) { + boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); + if(state == EClientState::DISCONNECTING) { assert(0); //Should not be possible - socket must be closed at this point @@ -874,33 +882,35 @@ void CServerHandler::onPacketReceived(const std::shared_ptr void CServerHandler::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { - networkConnection.reset(); - if(state == EClientState::DISCONNECTING) { - logNetwork->info("Successfully closed connection to server, ending listening thread!"); + assert(networkConnection == nullptr); + // Note: this branch can be reached on app shutdown, when main thread holds mutex till destruction + logNetwork->info("Successfully closed connection to server!"); + return; + } + + boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); + + logNetwork->error("Lost connection to server! Connection has been closed"); + networkConnection.reset(); + + if(client) + { + state = EClientState::DISCONNECTING; + + GH.dispatchMainThread([]() + { + CSH->endGameplay(); + GH.defActionsDef = 63; + CMM->menu->switchToTab("main"); + }); } else { - logNetwork->error("Lost connection to server, ending listening thread! Connection has been closed"); - - if(client) - { - state = EClientState::DISCONNECTING; - - GH.dispatchMainThread([]() - { - CSH->endGameplay(); - GH.defActionsDef = 63; - CMM->menu->switchToTab("main"); - }); - } - else - { - LobbyClientDisconnected lcd; - lcd.clientId = c->connectionID; - applyPackOnLobbyScreen(lcd); - } + LobbyClientDisconnected lcd; + lcd.clientId = c->connectionID; + applyPackOnLobbyScreen(lcd); } } diff --git a/client/Client.cpp b/client/Client.cpp index 0233a0d84..a68608fee 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -521,7 +521,6 @@ void CClient::handlePack(CPack * pack) CBaseForCLApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack)); //find the applier if(apply) { - boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); apply->applyOnClBefore(this, pack); logNetwork->trace("\tMade first apply on cl: %s", typeid(pack).name()); gs->apply(pack); diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index dd4540ab0..94923d8f8 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -207,6 +207,8 @@ void GlobalLobbyClient::receiveJoinRoomSuccess(const JsonNode & json) void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr & connection) { + boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); + networkConnection = connection; JsonNode toSend; @@ -238,6 +240,8 @@ void GlobalLobbyClient::sendClientLogin() void GlobalLobbyClient::onConnectionFailed(const std::string & errorMessage) { + boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); + auto loginWindowPtr = loginWindow.lock(); if(!loginWindowPtr || !GH.windows().topWindow()) @@ -249,6 +253,8 @@ void GlobalLobbyClient::onConnectionFailed(const std::string & errorMessage) void GlobalLobbyClient::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { + boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); + assert(connection == networkConnection); networkConnection.reset(); diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 6e11692a9..1489fd888 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -261,10 +261,11 @@ bool CVCMIServer::prepareToStartGame() Load::ProgressAccumulator progressTracking; Load::Progress current(1); progressTracking.include(current); - auto currentProgress = std::numeric_limits::max(); - - auto progressTrackingThread = boost::thread([this, &progressTracking, ¤tProgress]() + + auto progressTrackingThread = boost::thread([this, &progressTracking]() { + auto currentProgress = std::numeric_limits::max(); + while(!progressTracking.finished()) { if(progressTracking.get() != currentProgress) From 7dee24edae2cda77de4ff4d05e683cd5f1516b22 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 3 Feb 2024 22:59:56 +0200 Subject: [PATCH 64/98] Cleanup --- client/CServerHandler.cpp | 9 +++- client/CServerHandler.h | 10 ++-- client/LobbyClientNetPackVisitors.h | 26 +++++----- client/globalLobby/GlobalLobbyClient.cpp | 3 +- client/globalLobby/GlobalLobbyDefines.h | 1 - lib/network/NetworkConnection.cpp | 26 ++++------ lib/network/NetworkConnection.h | 1 - lobby/LobbyDatabase.cpp | 5 -- lobby/LobbyDatabase.h | 2 - lobby/LobbyServer.cpp | 2 +- server/GlobalLobbyProcessor.h | 2 +- server/LobbyNetPackVisitors.h | 66 ++++++++++++------------ server/NetPacksLobbyServer.cpp | 4 +- 13 files changed, 74 insertions(+), 83 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index dbb5d01c4..273c1d13f 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -139,9 +139,9 @@ CServerHandler::~CServerHandler() } CServerHandler::CServerHandler() - : applier(std::make_unique>()) + : networkHandler(INetworkHandler::createHandler()) , lobbyClient(std::make_unique()) - , networkHandler(INetworkHandler::createHandler()) + , applier(std::make_unique>()) , threadNetwork(&CServerHandler::threadRunNetwork, this) , state(EClientState::NONE) , campaignStateToSend(nullptr) @@ -187,6 +187,11 @@ GlobalLobbyClient & CServerHandler::getGlobalLobby() return *lobbyClient; } +INetworkHandler & CServerHandler::getNetworkHandler() +{ + return *networkHandler; +} + void CServerHandler::startLocalServerAndConnect(bool connectToLobby) { if(threadRunLocalServer.joinable()) diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 22d8d9535..0e8ac1c5d 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -97,6 +97,7 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor { friend class ApplyOnLobbyHandlerNetPackVisitor; + std::unique_ptr networkHandler; std::shared_ptr networkConnection; std::unique_ptr lobbyClient; std::unique_ptr> applier; @@ -104,6 +105,9 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor std::vector myNames; std::shared_ptr highScoreCalc; + boost::thread threadRunLocalServer; + boost::thread threadNetwork; + void threadRunNetwork(); void threadRunServer(bool connectToLobby); @@ -123,8 +127,6 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor bool isServerLocal() const; public: - std::unique_ptr networkHandler; - std::shared_ptr c; std::atomic state; @@ -140,9 +142,6 @@ public: //////////////////// std::unique_ptr th; - boost::thread threadRunLocalServer; - boost::thread threadNetwork; - std::unique_ptr client; CServerHandler(); @@ -153,6 +152,7 @@ public: void connectToServer(const std::string & addr, const ui16 port); GlobalLobbyClient & getGlobalLobby(); + INetworkHandler & getNetworkHandler(); // Helpers for lobby state access std::set getHumanColors(); diff --git a/client/LobbyClientNetPackVisitors.h b/client/LobbyClientNetPackVisitors.h index 041e06a4b..b3996cce7 100644 --- a/client/LobbyClientNetPackVisitors.h +++ b/client/LobbyClientNetPackVisitors.h @@ -32,12 +32,12 @@ public: bool getResult() const { return result; } - virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; - virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbyRestartGame(LobbyRestartGame & pack) override; - virtual void visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) override; - virtual void visitLobbyStartGame(LobbyStartGame & pack) override; - virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override; + void visitLobbyClientConnected(LobbyClientConnected & pack) override; + void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; + void visitLobbyRestartGame(LobbyRestartGame & pack) override; + void visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) override; + void visitLobbyStartGame(LobbyStartGame & pack) override; + void visitLobbyUpdateState(LobbyUpdateState & pack) override; }; class ApplyOnLobbyScreenNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) @@ -52,11 +52,11 @@ public: { } - virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbyChatMessage(LobbyChatMessage & pack) override; - virtual void visitLobbyGuiAction(LobbyGuiAction & pack) override; - virtual void visitLobbyStartGame(LobbyStartGame & pack) override; - virtual void visitLobbyLoadProgress(LobbyLoadProgress & pack) override; - virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override; - virtual void visitLobbyShowMessage(LobbyShowMessage & pack) override; + void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; + void visitLobbyChatMessage(LobbyChatMessage & pack) override; + void visitLobbyGuiAction(LobbyGuiAction & pack) override; + void visitLobbyStartGame(LobbyStartGame & pack) override; + void visitLobbyLoadProgress(LobbyLoadProgress & pack) override; + void visitLobbyUpdateState(LobbyUpdateState & pack) override; + void visitLobbyShowMessage(LobbyShowMessage & pack) override; }; diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index 94923d8f8..687e77392 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -177,7 +177,6 @@ void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json) room.hostAccountID = jsonEntry["hostAccountID"].String(); room.hostAccountDisplayName = jsonEntry["hostAccountDisplayName"].String(); room.description = jsonEntry["description"].String(); -// room.status = jsonEntry["status"].String(); room.playersCount = jsonEntry["playersCount"].Integer(); room.playersLimit = jsonEntry["playersLimit"].Integer(); @@ -289,7 +288,7 @@ void GlobalLobbyClient::connect() { std::string hostname = settings["lobby"]["hostname"].String(); int16_t port = settings["lobby"]["port"].Integer(); - CSH->networkHandler->connectToRemote(*this, hostname, port); + CSH->getNetworkHandler().connectToRemote(*this, hostname, port); } bool GlobalLobbyClient::isConnected() diff --git a/client/globalLobby/GlobalLobbyDefines.h b/client/globalLobby/GlobalLobbyDefines.h index 83c79c045..ae61d3516 100644 --- a/client/globalLobby/GlobalLobbyDefines.h +++ b/client/globalLobby/GlobalLobbyDefines.h @@ -22,7 +22,6 @@ struct GlobalLobbyRoom std::string hostAccountID; std::string hostAccountDisplayName; std::string description; -// std::string status; int playersCount; int playersLimit; }; diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index ebeb06778..a9e2398ad 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -29,24 +29,14 @@ void NetworkConnection::start() [self = shared_from_this()](const auto & ec, const auto & endpoint) { self->onHeaderReceived(ec); }); } -void NetworkConnection::onHeaderReceived(const boost::system::error_code & ec) +void NetworkConnection::onHeaderReceived(const boost::system::error_code & ecHeader) { - if (ec) + if (ecHeader) { - listener.onDisconnected(shared_from_this(), ec.message()); + listener.onDisconnected(shared_from_this(), ecHeader.message()); return; } - uint32_t messageSize = readPacketSize(); - - boost::asio::async_read(*socket, - readBuffer, - boost::asio::transfer_exactly(messageSize), - [self = shared_from_this(), messageSize](const auto & ec, const auto & endpoint) { self->onPacketReceived(ec, messageSize); }); -} - -uint32_t NetworkConnection::readPacketSize() -{ if (readBuffer.size() < messageHeaderSize) throw std::runtime_error("Failed to read header!"); @@ -54,9 +44,15 @@ uint32_t NetworkConnection::readPacketSize() readBuffer.sgetn(reinterpret_cast(&messageSize), sizeof(messageSize)); if (messageSize > messageMaxSize) + { listener.onDisconnected(shared_from_this(), "Invalid packet size!"); + return; + } - return messageSize; + boost::asio::async_read(*socket, + readBuffer, + boost::asio::transfer_exactly(messageSize), + [self = shared_from_this(), messageSize](const auto & ecPayload, const auto & endpoint) { self->onPacketReceived(ecPayload, messageSize); }); } void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, uint32_t expectedPacketSize) @@ -69,7 +65,7 @@ void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, u if (readBuffer.size() < expectedPacketSize) { - throw std::runtime_error("Failed to read header!"); + throw std::runtime_error("Failed to read packet!"); } std::vector message(expectedPacketSize); diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h index 4f7010ea1..beaaba376 100644 --- a/lib/network/NetworkConnection.h +++ b/lib/network/NetworkConnection.h @@ -25,7 +25,6 @@ class NetworkConnection : public INetworkConnection, public std::enable_shared_f void onHeaderReceived(const boost::system::error_code & ec); void onPacketReceived(const boost::system::error_code & ec, uint32_t expectedPacketSize); - uint32_t readPacketSize(); public: NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr & socket); diff --git a/lobby/LobbyDatabase.cpp b/lobby/LobbyDatabase.cpp index 308089631..3e3b29869 100644 --- a/lobby/LobbyDatabase.cpp +++ b/lobby/LobbyDatabase.cpp @@ -397,11 +397,6 @@ std::string LobbyDatabase::getAccountDisplayName(const std::string & accountID) return result; } -//LobbyCookieStatus LobbyDatabase::getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID) -//{ -// return {}; -//} - LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID) { bool result = false; diff --git a/lobby/LobbyDatabase.h b/lobby/LobbyDatabase.h index 8825b9063..02039879c 100644 --- a/lobby/LobbyDatabase.h +++ b/lobby/LobbyDatabase.h @@ -78,14 +78,12 @@ public: std::vector getActiveGameRooms(); std::vector getActiveAccounts(); -// std::vector getAccountsInRoom(const std::string & roomID); std::vector getRecentMessageHistory(); std::string getIdleGameRoom(const std::string & hostAccountID); std::string getAccountGameRoom(const std::string & accountID); std::string getAccountDisplayName(const std::string & accountID); -// LobbyCookieStatus getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID); LobbyCookieStatus getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID); LobbyInviteStatus getAccountInviteStatus(const std::string & accountID, const std::string & roomID); LobbyRoomState getGameRoomStatus(const std::string & roomID); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index e02f64366..15078de65 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -217,7 +217,7 @@ void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection, const if(activeProxies.count(connection)) { - auto & otherConnection = activeProxies.at(connection); + const auto & otherConnection = activeProxies.at(connection); if (otherConnection) otherConnection->close(); diff --git a/server/GlobalLobbyProcessor.h b/server/GlobalLobbyProcessor.h index 87cb15bbb..74267d160 100644 --- a/server/GlobalLobbyProcessor.h +++ b/server/GlobalLobbyProcessor.h @@ -37,5 +37,5 @@ class GlobalLobbyProcessor : public INetworkClientListener void establishNewConnection(); public: - GlobalLobbyProcessor(CVCMIServer & owner); + explicit GlobalLobbyProcessor(CVCMIServer & owner); }; diff --git a/server/LobbyNetPackVisitors.h b/server/LobbyNetPackVisitors.h index 3e3d2f1e3..ed0a566bd 100644 --- a/server/LobbyNetPackVisitors.h +++ b/server/LobbyNetPackVisitors.h @@ -28,16 +28,16 @@ public: return result; } - virtual void visitForLobby(CPackForLobby & pack) override; - virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; - virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbyRestartGame(LobbyRestartGame & pack) override; - virtual void visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) override; - virtual void visitLobbyStartGame(LobbyStartGame & pack) override; - virtual void visitLobbyChangeHost(LobbyChangeHost & pack) override; - virtual void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override; - virtual void visitLobbyChatMessage(LobbyChatMessage & pack) override; - virtual void visitLobbyGuiAction(LobbyGuiAction & pack) override; + void visitForLobby(CPackForLobby & pack) override; + void visitLobbyClientConnected(LobbyClientConnected & pack) override; + void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; + void visitLobbyRestartGame(LobbyRestartGame & pack) override; + void visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) override; + void visitLobbyStartGame(LobbyStartGame & pack) override; + void visitLobbyChangeHost(LobbyChangeHost & pack) override; + void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override; + void visitLobbyChatMessage(LobbyChatMessage & pack) override; + void visitLobbyGuiAction(LobbyGuiAction & pack) override; }; class ApplyOnServerAfterAnnounceNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) @@ -51,12 +51,12 @@ public: { } - virtual void visitForLobby(CPackForLobby & pack) override; - virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; - virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbyRestartGame(LobbyRestartGame & pack) override; - virtual void visitLobbyStartGame(LobbyStartGame & pack) override; - virtual void visitLobbyChangeHost(LobbyChangeHost & pack) override; + void visitForLobby(CPackForLobby & pack) override; + void visitLobbyClientConnected(LobbyClientConnected & pack) override; + void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; + void visitLobbyRestartGame(LobbyRestartGame & pack) override; + void visitLobbyStartGame(LobbyStartGame & pack) override; + void visitLobbyChangeHost(LobbyChangeHost & pack) override; }; class ApplyOnServerNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) @@ -76,21 +76,21 @@ public: return result; } - virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; - virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbySetMap(LobbySetMap & pack) override; - virtual void visitLobbySetCampaign(LobbySetCampaign & pack) override; - virtual void visitLobbySetCampaignMap(LobbySetCampaignMap & pack) override; - virtual void visitLobbySetCampaignBonus(LobbySetCampaignBonus & pack) override; - virtual void visitLobbyRestartGame(LobbyRestartGame & pack) override; - virtual void visitLobbyStartGame(LobbyStartGame & pack) override; - virtual void visitLobbyChangeHost(LobbyChangeHost & pack) override; - virtual void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override; - virtual void visitLobbySetPlayer(LobbySetPlayer & pack) override; - virtual void visitLobbySetPlayerName(LobbySetPlayerName & pack) override; - virtual void visitLobbySetTurnTime(LobbySetTurnTime & pack) override; - virtual void visitLobbySetExtraOptions(LobbySetExtraOptions & pack) override; - virtual void visitLobbySetSimturns(LobbySetSimturns & pack) override; - virtual void visitLobbySetDifficulty(LobbySetDifficulty & pack) override; - virtual void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) override; + void visitLobbyClientConnected(LobbyClientConnected & pack) override; + void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; + void visitLobbySetMap(LobbySetMap & pack) override; + void visitLobbySetCampaign(LobbySetCampaign & pack) override; + void visitLobbySetCampaignMap(LobbySetCampaignMap & pack) override; + void visitLobbySetCampaignBonus(LobbySetCampaignBonus & pack) override; + void visitLobbyRestartGame(LobbyRestartGame & pack) override; + void visitLobbyStartGame(LobbyStartGame & pack) override; + void visitLobbyChangeHost(LobbyChangeHost & pack) override; + void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override; + void visitLobbySetPlayer(LobbySetPlayer & pack) override; + void visitLobbySetPlayerName(LobbySetPlayerName & pack) override; + void visitLobbySetTurnTime(LobbySetTurnTime & pack) override; + void visitLobbySetExtraOptions(LobbySetExtraOptions & pack) override; + void visitLobbySetSimturns(LobbySetSimturns & pack) override; + void visitLobbySetDifficulty(LobbySetDifficulty & pack) override; + void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) override; }; diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index d0c934e38..d312befdd 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -206,7 +206,7 @@ void ApplyOnServerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) { - for(auto & c : srv.activeConnections) + for(const auto & c : srv.activeConnections) c->enterLobbyConnectionMode(); } @@ -250,7 +250,7 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyStartGame(LobbyStartGam srv.startGameImmediately(); else { - for(auto & c : srv.activeConnections) + for(const auto & c : srv.activeConnections) { if(c->connectionID == pack.clientId) { From 6528124c1e6c6ddc1cd17b32d479f007a9a52fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sun, 4 Feb 2024 08:55:51 +0100 Subject: [PATCH 65/98] Do not fill water zone with obstacles --- lib/rmg/Zone.cpp | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index e48e3e43b..09f563727 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -116,6 +116,7 @@ void Zone::initFreeTiles() if(dAreaFree.empty()) { + // Fixme: This might fail fot water zone, which doesn't need to have a tile in its center of the mass dAreaPossible.erase(pos); dAreaFree.add(pos); //zone must have at least one free tile where other paths go - for instance in the center } @@ -250,21 +251,30 @@ void Zone::fractalize() treasureDensity += t.density; } - if (treasureValue > 400) + if (getType() == ETemplateZoneType::WATER) { - // A quater at max density - marginFactor = (0.25f + ((std::max(0, (600 - treasureValue))) / (600.f - 400)) * 0.75f); + // Set very little obstacles on water + spanFactor = 0.2; } - else if (treasureValue < 125) + else //Scale with treasure density { - //Dense obstacles - spanFactor *= (treasureValue / 125.f); - vstd::amax(spanFactor, 0.15f); + if (treasureValue > 400) + { + // A quater at max density + marginFactor = (0.25f + ((std::max(0, (600 - treasureValue))) / (600.f - 400)) * 0.75f); + } + else if (treasureValue < 125) + { + //Dense obstacles + spanFactor *= (treasureValue / 125.f); + vstd::amax(spanFactor, 0.15f); + } + if (treasureDensity <= 10) + { + vstd::amin(spanFactor, 0.1f + 0.01f * treasureDensity); //Add extra obstacles to fill up space } - if (treasureDensity <= 10) - { - vstd::amin(spanFactor, 0.1f + 0.01f * treasureDensity); //Add extra obstacles to fill up space } + float blockDistance = minDistance * spanFactor; //More obstacles in the Underground freeDistance = freeDistance * marginFactor; vstd::amax(freeDistance, 4 * 4); From 7ce3553a6ddaa9d367620c356b3ba6c49dcc9345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sun, 4 Feb 2024 08:56:21 +0100 Subject: [PATCH 66/98] Fix race condition when placing object at teh shore --- lib/rmg/modificators/ObjectManager.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/rmg/modificators/ObjectManager.cpp b/lib/rmg/modificators/ObjectManager.cpp index 6bc51579c..8dcc24c25 100644 --- a/lib/rmg/modificators/ObjectManager.cpp +++ b/lib/rmg/modificators/ObjectManager.cpp @@ -579,11 +579,15 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD for (auto id : adjacentZones) { - auto manager = map.getZones().at(id)->getModificator(); - if (manager) + auto otherZone = map.getZones().at(id); + if ((otherZone->getType() == ETemplateZoneType::WATER) == (zone.getType() == ETemplateZoneType::WATER)) { - // TODO: Update distances for perimeter of guarded object, not just treasures - manager->updateDistances(object); + // Do not update other zone if only one is water + auto manager = otherZone->getModificator(); + if (manager) + { + manager->updateDistances(object); + } } } } From af3c6450a7869a1e78c15237b5b48ec11179a6d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sun, 4 Feb 2024 08:56:45 +0100 Subject: [PATCH 67/98] Update comments --- lib/rmg/CZonePlacer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 6bcc27d64..9765a7b4e 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -908,7 +908,7 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, closestZone->getPos().z)); //Closest vertex belongs to zone } - //Assign actual tiles to each zone using nonlinear norm for fine edges + //Assign actual tiles to each zone for (pos.z = 0; pos.z < levels; pos.z++) { for (pos.x = 0; pos.x < width; pos.x++) @@ -918,7 +918,6 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) distances.clear(); for(const auto & zoneVertex : vertexMapping) { - // FIXME: Find closest vertex, not closest zone auto zone = zoneVertex.first; for (const auto & vertex : zoneVertex.second) { @@ -929,7 +928,8 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) } } - auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first; //closest tile belongs to zone + //Tile closes to vertex belongs to zone + auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first; closestZone->area().add(pos); map.setZoneID(pos, closestZone->getId()); } From 5c6a146952fdffe8408e9d000f14f09426f55753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sun, 4 Feb 2024 08:57:02 +0100 Subject: [PATCH 68/98] Use HoTA values for water treasures --- config/randomMap.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config/randomMap.json b/config/randomMap.json index 800a2f893..70a4d0dd6 100644 --- a/config/randomMap.json +++ b/config/randomMap.json @@ -3,8 +3,9 @@ { "treasure" : [ - { "min" : 2000, "max" : 6000, "density" : 1 }, - { "min" : 100, "max" : 1000, "density" : 5 } + { "min" : 4000, "max" : 12000, "density" : 1 }, + { "min" : 1000, "max" : 4000, "density" : 3 }, + { "min" : 100, "max" : 1000, "density" : 6 } ], "shipyard" : { From d4bedd8d8dda288c49b87217b1c37d656ccdecfc Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 4 Feb 2024 19:56:04 +0200 Subject: [PATCH 69/98] Fixed handling of match server crash --- Mods/vcmi/config/vcmi/english.json | 3 +- client/CPlayerInterface.cpp | 11 +-- client/CServerHandler.cpp | 73 ++++++++++--------- client/CServerHandler.h | 7 +- client/NetPacksLobbyClient.cpp | 17 ++--- client/lobby/CBonusSelection.cpp | 9 +-- client/lobby/CLobbyScreen.cpp | 2 +- client/mainmenu/CMainMenu.cpp | 13 +--- .../windows/settings/SettingsMainWindow.cpp | 9 +-- lib/network/NetworkConnection.cpp | 6 +- 10 files changed, 69 insertions(+), 81 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index b37e5b875..bca221500 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -63,7 +63,6 @@ "vcmi.mainMenu.serverClosing" : "Closing...", "vcmi.mainMenu.hostTCP" : "Host TCP/IP game", "vcmi.mainMenu.joinTCP" : "Join TCP/IP game", - "vcmi.mainMenu.playerName" : "Player", "vcmi.lobby.filepath" : "File path", "vcmi.lobby.creationDate" : "Creation date", @@ -94,10 +93,10 @@ "vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s", "vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.", + "vcmi.server.errors.disconnected" : "{Network Error}\n\nConnection to game server has been lost!", "vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.", "vcmi.server.errors.modsToEnable" : "{Following mods are required}", "vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}", - "vcmi.server.confirmReconnect" : "Do you want to reconnect to the last session?", "vcmi.server.errors.modNoDependency" : "Failed to load mod {'%s'}!\n It depends on mod {'%s'} which is not active!\n", "vcmi.server.errors.modConflict" : "Failed to load mod {'%s'}!\n Conflicts with active mod {'%s'}!\n", "vcmi.server.errors.unknownEntity" : "Failed to load save! Unknown entity '%s' found in saved game! Save may not be compatible with currently installed version of mods!", diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 5b0a37966..3d08ad7d9 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1870,14 +1870,9 @@ void CPlayerInterface::proposeLoadingGame() CGI->generaltexth->allTexts[68], []() { - GH.dispatchMainThread( - []() - { - CSH->endGameplay(); - GH.defActionsDef = 63; - CMM->menu->switchToTab("load"); - } - ); + CSH->endGameplay(); + GH.defActionsDef = 63; + CMM->menu->switchToTab("load"); }, nullptr ); diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 273c1d13f..a8b7deaf0 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -144,6 +144,7 @@ CServerHandler::CServerHandler() , applier(std::make_unique>()) , threadNetwork(&CServerHandler::threadRunNetwork, this) , state(EClientState::NONE) + , serverPort(0) , campaignStateToSend(nullptr) , screenType(ESelectionScreen::unknown) , serverMode(EServerMode::NONE) @@ -165,7 +166,7 @@ void CServerHandler::threadRunNetwork() void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode newServerMode, const std::vector & names) { hostClientId = -1; - state = EClientState::NONE; + setState(EClientState::NONE); serverMode = newServerMode; mapToStart = nullptr; th = std::make_unique(); @@ -263,7 +264,7 @@ void CServerHandler::startLocalServerAndConnect(bool connectToLobby) void CServerHandler::connectToServer(const std::string & addr, const ui16 port) { logNetwork->info("Establishing connection to %s:%d...", addr, port); - state = EClientState::CONNECTING; + setState(EClientState::CONNECTING); serverHostname = addr; serverPort = port; @@ -281,7 +282,7 @@ void CServerHandler::connectToServer(const std::string & addr, const ui16 port) void CServerHandler::onConnectionFailed(const std::string & errorMessage) { - assert(state == EClientState::CONNECTING); + assert(getState() == EClientState::CONNECTING); boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); if (isServerLocal()) @@ -293,7 +294,7 @@ void CServerHandler::onConnectionFailed(const std::string & errorMessage) else { // remote server refused connection - show error message - state = EClientState::CONNECTION_FAILED; + setState(EClientState::NONE); CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {}); } } @@ -302,7 +303,7 @@ void CServerHandler::onTimer() { boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); - if(state == EClientState::CONNECTION_CANCELLED) + if(getState() == EClientState::CONNECTION_CANCELLED) { logNetwork->info("Connection aborted by player!"); return; @@ -314,7 +315,7 @@ void CServerHandler::onTimer() void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netConnection) { - assert(state == EClientState::CONNECTING); + assert(getState() == EClientState::CONNECTING); boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); @@ -361,6 +362,16 @@ ui8 CServerHandler::myFirstId() const return clientFirstId(c->connectionID); } +EClientState CServerHandler::getState() const +{ + return state; +} + +void CServerHandler::setState(EClientState newState) +{ + state = newState; +} + bool CServerHandler::isServerLocal() const { return threadRunLocalServer.joinable(); @@ -418,13 +429,13 @@ void CServerHandler::sendClientConnecting() const void CServerHandler::sendClientDisconnecting() { // FIXME: This is workaround needed to make sure client not trying to sent anything to non existed server - if(state == EClientState::DISCONNECTING) + if(getState() == EClientState::DISCONNECTING) { assert(0); return; } - state = EClientState::DISCONNECTING; + setState(EClientState::DISCONNECTING); mapToStart = nullptr; LobbyClientDisconnected lcd; lcd.clientId = c->connectionID; @@ -439,13 +450,14 @@ void CServerHandler::sendClientDisconnecting() logNetwork->info("Sent leaving signal to the server"); } sendLobbyPack(lcd); + networkConnection->close(); networkConnection.reset(); c.reset(); } void CServerHandler::setCampaignState(std::shared_ptr newCampaign) { - state = EClientState::LOBBY_CAMPAIGN; + setState(EClientState::LOBBY_CAMPAIGN); LobbySetCampaign lsc; lsc.ourCampaign = newCampaign; sendLobbyPack(lsc); @@ -453,7 +465,7 @@ void CServerHandler::setCampaignState(std::shared_ptr newCampaign void CServerHandler::setCampaignMap(CampaignScenarioID mapId) const { - if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place + if(getState() == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place return; LobbySetCampaignMap lscm; @@ -463,7 +475,7 @@ void CServerHandler::setCampaignMap(CampaignScenarioID mapId) const void CServerHandler::setCampaignBonus(int bonusId) const { - if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place + if(getState() == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place return; LobbySetCampaignBonus lscb; @@ -673,7 +685,7 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta } // After everything initialized we can accept CPackToClient netpacks c->enterGameplayConnectionMode(client->gameState()); - state = EClientState::GAMEPLAY; + setState(EClientState::GAMEPLAY); } void CServerHandler::endGameplay() @@ -780,7 +792,7 @@ int CServerHandler::howManyPlayerInterfaces() ELoadMode CServerHandler::getLoadMode() { - if(loadMode != ELoadMode::TUTORIAL && state == EClientState::GAMEPLAY) + if(loadMode != ELoadMode::TUTORIAL && getState() == EClientState::GAMEPLAY) { if(si->campState) return ELoadMode::CAMPAIGN; @@ -874,7 +886,7 @@ void CServerHandler::onPacketReceived(const std::shared_ptr { boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); - if(state == EClientState::DISCONNECTING) + if(getState() == EClientState::DISCONNECTING) { assert(0); //Should not be possible - socket must be closed at this point return; @@ -887,7 +899,7 @@ void CServerHandler::onPacketReceived(const std::shared_ptr void CServerHandler::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { - if(state == EClientState::DISCONNECTING) + if(getState() == EClientState::DISCONNECTING) { assert(networkConnection == nullptr); // Note: this branch can be reached on app shutdown, when main thread holds mutex till destruction @@ -898,18 +910,13 @@ void CServerHandler::onDisconnected(const std::shared_ptr & boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); logNetwork->error("Lost connection to server! Connection has been closed"); - networkConnection.reset(); if(client) { - state = EClientState::DISCONNECTING; - - GH.dispatchMainThread([]() - { - CSH->endGameplay(); - GH.defActionsDef = 63; - CMM->menu->switchToTab("main"); - }); + CSH->endGameplay(); + GH.defActionsDef = 63; + CMM->menu->switchToTab("main"); + CSH->showServerError(CGI->generaltexth->translate("vcmi.server.errors.disconnected")); } else { @@ -917,6 +924,8 @@ void CServerHandler::onDisconnected(const std::shared_ptr & lcd.clientId = c->connectionID; applyPackOnLobbyScreen(lcd); } + + networkConnection.reset(); } void CServerHandler::visitForLobby(CPackForLobby & lobbyPack) @@ -970,15 +979,13 @@ void CServerHandler::threadRunServer(bool connectToLobby) } else { - if (state != EClientState::DISCONNECTING) - { - if (state == EClientState::CONNECTING) - CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.server.errors.existingProcess"), {}); - else - CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.server.errors.serverCrashed"), {}); - } + boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); - state = EClientState::CONNECTION_CANCELLED; // stop attempts to reconnect + if (getState() == EClientState::CONNECTING) + { + showServerError(CGI->generaltexth->translate("vcmi.server.errors.existingProcess")); + setState(EClientState::CONNECTION_CANCELLED); // stop attempts to reconnect + } logNetwork->error("Error: server failed to close correctly or crashed!"); logNetwork->error("Check %s for more info", logName); } @@ -987,6 +994,6 @@ void CServerHandler::threadRunServer(bool connectToLobby) void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const { - if(state != EClientState::STARTING) + if(getState() != EClientState::STARTING) c->sendPack(&pack); } diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 0e8ac1c5d..62ce39f64 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -54,7 +54,6 @@ enum class EClientState : ui8 STARTING, // Gameplay interfaces being created, we pause netpacks retrieving GAMEPLAY, // In-game, used by some UI DISCONNECTING, // We disconnecting, drop all netpacks - CONNECTION_FAILED // We could not connect to server }; enum class EServerMode : uint8_t @@ -108,6 +107,8 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor boost::thread threadRunLocalServer; boost::thread threadNetwork; + std::atomic state; + void threadRunNetwork(); void threadRunServer(bool connectToLobby); @@ -129,7 +130,6 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor public: std::shared_ptr c; - std::atomic state; //////////////////// // FIXME: Bunch of crutches to glue it all together @@ -160,6 +160,9 @@ public: bool isMyColor(PlayerColor color) const; ui8 myFirstId() const; // Used by chat only! + EClientState getState() const; + void setState(EClientState newState); + bool isHost() const; bool isGuest() const; diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index 9165df5d2..6ea57b941 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -73,7 +73,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon GH.windows().createAndPushWindow(handler.screenType); } - handler.state = EClientState::LOBBY; + handler.setState(EClientState::LOBBY); } } @@ -136,13 +136,10 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) { - if(handler.state == EClientState::GAMEPLAY) - { - handler.restartGameplay(); - } - - if (handler.validateGameStart()) - handler.sendStartGame(); + assert(handler.getState() == EClientState::GAMEPLAY); + + handler.restartGameplay(); + handler.sendStartGame(); } void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) @@ -160,7 +157,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pac return; } - handler.state = EClientState::STARTING; + handler.setState(EClientState::STARTING); if(handler.si->mode != EStartMode::LOAD_GAME || pack.clientId == handler.c->connectionID) { auto modeBackup = handler.si->mode; @@ -206,7 +203,7 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState & if(!lobby) //stub: ignore message for game mode return; - if(!lobby->bonusSel && handler.si->campState && handler.state == EClientState::LOBBY_CAMPAIGN) + if(!lobby->bonusSel && handler.si->campState && handler.getState() == EClientState::LOBBY_CAMPAIGN) { lobby->bonusSel = std::make_shared(); GH.windows().pushWindow(lobby->bonusSel); diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 0939e776b..3352d013f 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -307,15 +307,12 @@ void CBonusSelection::createBonusesIcons() void CBonusSelection::updateAfterStateChange() { - if(CSH->state != EClientState::GAMEPLAY) + if(CSH->getState() != EClientState::GAMEPLAY) { buttonRestart->disable(); buttonVideo->disable(); buttonStart->enable(); - if(!getCampaign()->conqueredScenarios().empty()) - buttonBack->block(true); - else - buttonBack->block(false); + buttonBack->block(!getCampaign()->conqueredScenarios().empty()); } else { @@ -358,7 +355,7 @@ void CBonusSelection::updateAfterStateChange() void CBonusSelection::goBack() { - if(CSH->state != EClientState::GAMEPLAY) + if(CSH->getState() != EClientState::GAMEPLAY) { GH.windows().popWindows(2); } diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index 15a2cd7f3..63cda2fd1 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -112,7 +112,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType) CLobbyScreen::~CLobbyScreen() { // TODO: For now we always destroy whole lobby when leaving bonus selection screen - if(CSH->state == EClientState::LOBBY_CAMPAIGN) + if(CSH->getState() == EClientState::LOBBY_CAMPAIGN) CSH->sendClientDisconnecting(); } diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 254ce62d5..4ff244b9b 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -490,7 +490,7 @@ std::string CMultiMode::getPlayerName() { std::string name = settings["general"]["playerName"].String(); if(name == "Player") - name = CGI->generaltexth->translate("vcmi.mainMenu.playerName"); + name = CGI->generaltexth->translate("core.genrltxt.434"); return name; } @@ -588,15 +588,8 @@ void CSimpleJoinScreen::connectToServer() void CSimpleJoinScreen::leaveScreen() { - if(CSH->state == EClientState::CONNECTING) - { - textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverClosing")); - CSH->state = EClientState::CONNECTION_CANCELLED; - } - else if(GH.windows().isTopWindow(this)) - { - close(); - } + textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverClosing")); + CSH->setState(EClientState::CONNECTION_CANCELLED); } void CSimpleJoinScreen::onChange(const std::string & newText) diff --git a/client/windows/settings/SettingsMainWindow.cpp b/client/windows/settings/SettingsMainWindow.cpp index d122c8a02..b9ccb0815 100644 --- a/client/windows/settings/SettingsMainWindow.cpp +++ b/client/windows/settings/SettingsMainWindow.cpp @@ -143,12 +143,9 @@ void SettingsMainWindow::mainMenuButtonCallback() [this]() { close(); - GH.dispatchMainThread( []() - { - CSH->endGameplay(); - GH.defActionsDef = 63; - CMM->menu->switchToTab("main"); - }); + CSH->endGameplay(); + GH.defActionsDef = 63; + CMM->menu->switchToTab("main"); }, 0 ); diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index a9e2398ad..01e20d951 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -79,13 +79,13 @@ void NetworkConnection::sendPacket(const std::vector & message) { boost::system::error_code ec; + // create array with single element - boost::asio::buffer can be constructed from containers, but not from plain integer std::array messageSize{static_cast(message.size())}; boost::asio::write(*socket, boost::asio::buffer(messageSize), ec ); boost::asio::write(*socket, boost::asio::buffer(message), ec ); - if (ec) - listener.onDisconnected(shared_from_this(), ec.message()); + //Note: ignoring error code, intended } void NetworkConnection::close() @@ -93,7 +93,7 @@ void NetworkConnection::close() boost::system::error_code ec; socket->close(ec); - //NOTE: ignoring error code + //NOTE: ignoring error code, intended } VCMI_LIB_NAMESPACE_END From b3c5882e11ce49f2181bb4318030225c517b015d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 4 Feb 2024 21:22:51 +0200 Subject: [PATCH 70/98] Workaround for broken save compatibility --- lib/registerTypes/RegisterTypesMapObjects.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/registerTypes/RegisterTypesMapObjects.h b/lib/registerTypes/RegisterTypesMapObjects.h index ddaac348a..5e8d96f32 100644 --- a/lib/registerTypes/RegisterTypesMapObjects.h +++ b/lib/registerTypes/RegisterTypesMapObjects.h @@ -54,7 +54,6 @@ void registerTypesMapObjects(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); - s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); @@ -133,6 +132,8 @@ void registerTypesMapObjects(Serializer &s) //s.template registerType(); s.template registerType(); + + s.template registerType(); } VCMI_LIB_NAMESPACE_END From 8cd683229772b89fad31e6532c0c00fb87926501 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 4 Feb 2024 21:23:21 +0200 Subject: [PATCH 71/98] Handle errors from global lobby on client --- client/globalLobby/GlobalLobbyClient.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index 687e77392..52acaee5d 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -66,7 +66,7 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptrerror("Received unexpected message from lobby server: %s", json["type"].String()); } void GlobalLobbyClient::receiveAccountCreated(const JsonNode & json) @@ -94,10 +94,10 @@ void GlobalLobbyClient::receiveOperationFailed(const JsonNode & json) { auto loginWindowPtr = loginWindow.lock(); - if(!loginWindowPtr || !GH.windows().topWindow()) - throw std::runtime_error("lobby connection finished without active login window!"); + if(loginWindowPtr) + loginWindowPtr->onConnectionFailed(json["reason"].String()); - loginWindowPtr->onConnectionFailed(json["reason"].String()); + // TODO: handle errors in lobby menu } void GlobalLobbyClient::receiveLoginSuccess(const JsonNode & json) @@ -257,7 +257,12 @@ void GlobalLobbyClient::onDisconnected(const std::shared_ptr assert(connection == networkConnection); networkConnection.reset(); - GH.windows().popWindows(1); + while (!GH.windows().findWindows().empty()) + { + // if global lobby is open, pop all dialogs on top of it as well as lobby itself + GH.windows().popWindows(1); + } + CInfoWindow::showInfoDialog("Connection to game lobby was lost!", {}); } From 9e5e7d95c356d364f8c2b4118e37381aa3ea05ce Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 4 Feb 2024 21:22:51 +0200 Subject: [PATCH 72/98] Workaround for broken save compatibility --- lib/registerTypes/RegisterTypesMapObjects.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/registerTypes/RegisterTypesMapObjects.h b/lib/registerTypes/RegisterTypesMapObjects.h index ddaac348a..5e8d96f32 100644 --- a/lib/registerTypes/RegisterTypesMapObjects.h +++ b/lib/registerTypes/RegisterTypesMapObjects.h @@ -54,7 +54,6 @@ void registerTypesMapObjects(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); - s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); @@ -133,6 +132,8 @@ void registerTypesMapObjects(Serializer &s) //s.template registerType(); s.template registerType(); + + s.template registerType(); } VCMI_LIB_NAMESPACE_END From 0495c52cf4e102055f8cd3b152d0fc58d3642bcd Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Sat, 3 Feb 2024 15:14:59 +0100 Subject: [PATCH 73/98] Don't call ScreenHandler::onScreenResize() when the window has been (un)maximized to avoid creating a huge texture and crashing --- client/eventsSDL/InputHandler.cpp | 4 ++-- client/gui/CGuiHandler.cpp | 7 +++++-- client/gui/CGuiHandler.h | 4 ++-- client/renderSDL/ScreenHandler.cpp | 1 + client/windows/settings/GeneralOptionsTab.cpp | 6 +++--- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/client/eventsSDL/InputHandler.cpp b/client/eventsSDL/InputHandler.cpp index adfbbde07..ae53dcf31 100644 --- a/client/eventsSDL/InputHandler.cpp +++ b/client/eventsSDL/InputHandler.cpp @@ -145,7 +145,7 @@ void InputHandler::preprocessEvent(const SDL_Event & ev) Settings full = settings.write["video"]["fullscreen"]; full->Bool() = !full->Bool(); - GH.onScreenResize(); + GH.onScreenResize(false); return; } } @@ -163,7 +163,7 @@ void InputHandler::preprocessEvent(const SDL_Event & ev) #ifndef VCMI_IOS { boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); - GH.onScreenResize(); + GH.onScreenResize(false); } #endif break; diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index 72bc72c37..2bffc662c 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -251,8 +251,11 @@ void CGuiHandler::setStatusbar(std::shared_ptr newStatusBar) currentStatusBar = newStatusBar; } -void CGuiHandler::onScreenResize() +void CGuiHandler::onScreenResize(bool resolutionChanged) { - screenHandler().onScreenResize(); + if(resolutionChanged) + { + screenHandler().onScreenResize(); + } windows().onScreenResize(); } diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h index 4f651c159..a8a555264 100644 --- a/client/gui/CGuiHandler.h +++ b/client/gui/CGuiHandler.h @@ -92,8 +92,8 @@ public: void init(); void renderFrame(); - /// called whenever user selects different resolution, requiring to center/resize all windows - void onScreenResize(); + /// called whenever SDL_WINDOWEVENT_RESTORED is reported or the user selects a different resolution, requiring to center/resize all windows + void onScreenResize(bool resolutionChanged); void handleEvents(); //takes events from queue and calls interested objects void fakeMouseMove(); diff --git a/client/renderSDL/ScreenHandler.cpp b/client/renderSDL/ScreenHandler.cpp index d3532a2d1..265c3b021 100644 --- a/client/renderSDL/ScreenHandler.cpp +++ b/client/renderSDL/ScreenHandler.cpp @@ -397,6 +397,7 @@ SDL_Window * ScreenHandler::createWindow() void ScreenHandler::onScreenResize() { + recreateWindowAndScreenBuffers(); } void ScreenHandler::validateSettings() diff --git a/client/windows/settings/GeneralOptionsTab.cpp b/client/windows/settings/GeneralOptionsTab.cpp index 69ee10fef..2058b664c 100644 --- a/client/windows/settings/GeneralOptionsTab.cpp +++ b/client/windows/settings/GeneralOptionsTab.cpp @@ -317,7 +317,7 @@ void GeneralOptionsTab::setGameResolution(int index) widget("resolutionLabel")->setText(resolutionToLabelString(resolution.x, resolution.y)); GH.dispatchMainThread([](){ - GH.onScreenResize(); + GH.onScreenResize(true); }); } @@ -341,7 +341,7 @@ void GeneralOptionsTab::setFullscreenMode(bool on, bool exclusive) updateResolutionSelector(); GH.dispatchMainThread([](){ - GH.onScreenResize(); + GH.onScreenResize(true); }); } @@ -400,7 +400,7 @@ void GeneralOptionsTab::setGameScaling(int index) widget("scalingLabel")->setText(scalingToLabelString(scaling)); GH.dispatchMainThread([](){ - GH.onScreenResize(); + GH.onScreenResize(true); }); } From a97d1d93771139a500bfa9e90d4a3e30d9b9941a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 5 Feb 2024 21:07:01 +0200 Subject: [PATCH 74/98] Fix crash on broken ENCHANTED bonus --- server/battles/BattleFlowProcessor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/battles/BattleFlowProcessor.cpp b/server/battles/BattleFlowProcessor.cpp index a27be3606..94d585803 100644 --- a/server/battles/BattleFlowProcessor.cpp +++ b/server/battles/BattleFlowProcessor.cpp @@ -584,10 +584,10 @@ void BattleFlowProcessor::stackEnchantedTrigger(const CBattleInfoCallback & batt auto bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTED))); for(auto b : bl) { - const CSpell * sp = b->subtype.as().toSpell(); - if(!sp) + if (!b->subtype.as().hasValue()) continue; + const CSpell * sp = b->subtype.as().toSpell(); const int32_t val = bl.valOfBonuses(Selector::typeSubtype(b->type, b->subtype)); const int32_t level = ((val > 3) ? (val - 3) : val); From 342e6daebda2c91296499424c764d637f821d3b2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 5 Feb 2024 21:07:50 +0200 Subject: [PATCH 75/98] Fix copy-paste error --- lib/constants/EntityIdentifiers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/constants/EntityIdentifiers.h b/lib/constants/EntityIdentifiers.h index 7bebcdf66..070d26492 100644 --- a/lib/constants/EntityIdentifiers.h +++ b/lib/constants/EntityIdentifiers.h @@ -330,7 +330,7 @@ public: static BuildingID FORT_LEVEL(unsigned int level) { assert(level < 3); - return BuildingID(Type::TOWN_HALL + level); + return BuildingID(Type::FORT + level); } static std::string encode(int32_t index); From 4af4d1a75ea0a31576f46573cdf45d07d8e45f0e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 5 Feb 2024 21:11:00 +0200 Subject: [PATCH 76/98] Remove excessive logging --- lib/mapping/CMapHeader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index 8144b857f..277a29c1c 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -149,7 +149,7 @@ void CMapHeader::registerMapStrings() if(maxStrings == 0 || mapLanguages.empty()) { - logGlobal->info("Map %s doesn't have any supported translation", name.toString()); + logGlobal->trace("Map %s doesn't have any supported translation", name.toString()); return; } From 87059be67bba0e8ebcf214fdb1b04a65a8289b68 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 5 Feb 2024 21:27:55 +0200 Subject: [PATCH 77/98] Added range checks to values read from h3m. Fixes reading of morale/luck values (-3..3) as unsigned leading to overflow. --- client/lobby/CSelectionBase.cpp | 2 +- client/lobby/RandomMapTab.cpp | 2 +- lib/CHeroHandler.h | 4 +- lib/constants/Enumerations.h | 8 +- lib/mapObjects/CGHeroInstance.h | 2 +- lib/mapping/CMapDefines.h | 4 +- lib/mapping/CMapHeader.cpp | 2 +- lib/mapping/CMapHeader.h | 11 ++- lib/mapping/MapFormatH3M.cpp | 160 ++++++++++++-------------------- lib/mapping/MapFormatH3M.h | 46 ++++++++- lib/mapping/MapReaderH3M.cpp | 35 ++++--- lib/mapping/MapReaderH3M.h | 17 ++-- lib/rmg/CMapGenerator.cpp | 2 +- 13 files changed, 154 insertions(+), 141 deletions(-) diff --git a/client/lobby/CSelectionBase.cpp b/client/lobby/CSelectionBase.cpp index f35ce9e18..c8ad4276c 100644 --- a/client/lobby/CSelectionBase.cpp +++ b/client/lobby/CSelectionBase.cpp @@ -229,7 +229,7 @@ void InfoCard::changeSelection() iconsLossCondition->setFrame(header->defeatIconIndex); labelLossConditionText->setText(header->defeatMessage.toString()); flagbox->recreate(); - labelDifficulty->setText(CGI->generaltexth->arraytxt[142 + mapInfo->mapHeader->difficulty]); + labelDifficulty->setText(CGI->generaltexth->arraytxt[142 + vstd::to_underlying(mapInfo->mapHeader->difficulty)]); iconDifficulty->setSelected(SEL->getCurrentDifficulty()); if(SEL->screenType == ESelectionScreen::loadGame || SEL->screenType == ESelectionScreen::saveGame) for(auto & button : iconDifficulty->buttons) diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 8696ba1fa..070319766 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -177,7 +177,7 @@ void RandomMapTab::updateMapInfoByHost() mapInfo->mapHeader->version = EMapFormat::VCMI; mapInfo->mapHeader->name.appendLocalString(EMetaText::GENERAL_TXT, 740); mapInfo->mapHeader->description.appendLocalString(EMetaText::GENERAL_TXT, 741); - mapInfo->mapHeader->difficulty = 1; // Normal + mapInfo->mapHeader->difficulty = EMapDifficulty::NORMAL; mapInfo->mapHeader->height = mapGenOptions->getHeight(); mapInfo->mapHeader->width = mapGenOptions->getWidth(); mapInfo->mapHeader->twoLevel = mapGenOptions->getHasTwoLevels(); diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 1a3219f8c..1b066d029 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -31,11 +31,11 @@ class CRandomGenerator; class JsonSerializeFormat; class BattleField; -enum class EHeroGender : uint8_t +enum class EHeroGender : int8_t { + DEFAULT = -1, // from h3m, instance has same gender as hero type MALE = 0, FEMALE = 1, - DEFAULT = 0xff // from h3m, instance has same gender as hero type }; class DLL_LINKAGE CHero : public HeroType diff --git a/lib/constants/Enumerations.h b/lib/constants/Enumerations.h index 67ddc1b4a..9c7b93f0e 100644 --- a/lib/constants/Enumerations.h +++ b/lib/constants/Enumerations.h @@ -64,10 +64,10 @@ enum class EMarketMode : int8_t enum class EAiTactic : int8_t { NONE = -1, - RANDOM, - WARRIOR, - BUILDER, - EXPLORER + RANDOM = 0, + WARRIOR = 1, + BUILDER = 2, + EXPLORER = 3 }; enum class EBuildingState : int8_t diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 61ca63750..5f6683a7f 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -23,7 +23,7 @@ class CGTownInstance; class CMap; struct TerrainTile; struct TurnInfo; -enum class EHeroGender : uint8_t; +enum class EHeroGender : int8_t; class DLL_LINKAGE CGHeroPlaceholder : public CGObjectInstance { diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index 27ad56a32..e1032aff7 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -37,8 +37,8 @@ public: MetaString message; TResources resources; ui8 players; // affected players, bit field? - ui8 humanAffected; - ui8 computerAffected; + bool humanAffected; + bool computerAffected; ui32 firstOccurence; ui32 nextOccurence; /// specifies after how many days the event will occur the next time; 0 if event occurs only one time diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index 277a29c1c..c67940ac5 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -117,7 +117,7 @@ void CMapHeader::setupEvents() } CMapHeader::CMapHeader() : version(EMapFormat::VCMI), height(72), width(72), - twoLevel(true), difficulty(1), levelLimit(0), howManyTeams(0), areAnyPlayers(false) + twoLevel(true), difficulty(EMapDifficulty::NORMAL), levelLimit(0), howManyTeams(0), areAnyPlayers(false) { setupEvents(); allowedHeroes = VLC->heroh->getDefaultAllowed(); diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index c343fcab7..3be7fc58e 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -191,6 +191,15 @@ struct DLL_LINKAGE TriggeredEvent } }; +enum class EMapDifficulty : uint8_t +{ + EASY = 0, + NORMAL = 1, + HARD = 2, + EXPERT = 3, + IMPOSSIBLE = 4 +}; + /// The map header holds information about loss/victory condition,map format, version, players, height, width,... class DLL_LINKAGE CMapHeader { @@ -218,7 +227,7 @@ public: bool twoLevel; /// The default value is true. MetaString name; MetaString description; - ui8 difficulty; /// The default value is 1 representing a normal map difficulty. + EMapDifficulty difficulty; /// Specifies the maximum level to reach for a hero. A value of 0 states that there is no /// maximum level for heroes. This is the default value. ui8 levelLimit; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index aabe26abc..c9a2a9b40 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -184,10 +184,10 @@ void CMapLoaderH3M::readHeader() mapHeader->twoLevel = reader->readBool(); mapHeader->name.appendTextID(readLocalizedString("header.name")); mapHeader->description.appendTextID(readLocalizedString("header.description")); - mapHeader->difficulty = reader->readInt8(); + mapHeader->difficulty = static_cast(reader->readInt8Checked(0, 4)); if(features.levelAB) - mapHeader->levelLimit = reader->readUInt8(); + mapHeader->levelLimit = reader->readInt8Checked(0, std::min(100u, VLC->heroh->maxSupportedLevel())); else mapHeader->levelLimit = 0; @@ -218,7 +218,7 @@ void CMapLoaderH3M::readPlayerInfo() continue; } - playerInfo.aiTactic = static_cast(reader->readUInt8()); + playerInfo.aiTactic = static_cast(reader->readInt8Checked(-1, 3)); if(features.levelSOD) reader->skipUnused(1); //TODO: check meaning? @@ -261,8 +261,8 @@ void CMapLoaderH3M::readPlayerInfo() if(features.levelAB) { reader->skipUnused(1); //TODO: check meaning? - uint32_t heroCount = reader->readUInt32(); - for(int pp = 0; pp < heroCount; ++pp) + size_t heroCount = reader->readUInt32(); + for(size_t pp = 0; pp < heroCount; ++pp) { SHeroName vv; vv.heroId = reader->readHero(); @@ -274,39 +274,13 @@ void CMapLoaderH3M::readPlayerInfo() } } -enum class EVictoryConditionType : uint8_t -{ - ARTIFACT = 0, - GATHERTROOP = 1, - GATHERRESOURCE = 2, - BUILDCITY = 3, - BUILDGRAIL = 4, - BEATHERO = 5, - CAPTURECITY = 6, - BEATMONSTER = 7, - TAKEDWELLINGS = 8, - TAKEMINES = 9, - TRANSPORTITEM = 10, - HOTA_ELIMINATE_ALL_MONSTERS = 11, - HOTA_SURVIVE_FOR_DAYS = 12, - WINSTANDARD = 255 -}; - -enum class ELossConditionType : uint8_t -{ - LOSSCASTLE = 0, - LOSSHERO = 1, - TIMEEXPIRES = 2, - LOSSSTANDARD = 255 -}; - void CMapLoaderH3M::readVictoryLossConditions() { mapHeader->triggeredEvents.clear(); mapHeader->victoryMessage.clear(); mapHeader->defeatMessage.clear(); - auto vicCondition = static_cast(reader->readUInt8()); + auto vicCondition = static_cast(reader->readInt8Checked(-1, 12)); EventCondition victoryCondition(EventCondition::STANDARD_WIN); EventCondition defeatCondition(EventCondition::DAYS_WITHOUT_TOWN); @@ -395,9 +369,9 @@ void CMapLoaderH3M::readVictoryLossConditions() EventExpression::OperatorAll oper; EventCondition cond(EventCondition::HAVE_BUILDING); cond.position = reader->readInt3(); - cond.objectType = BuildingID::HALL_LEVEL(reader->readUInt8() + 1); + cond.objectType = BuildingID::HALL_LEVEL(reader->readInt8Checked(0,3) + 1); oper.expressions.emplace_back(cond); - cond.objectType = BuildingID::FORT_LEVEL(reader->readUInt8()); + cond.objectType = BuildingID::FORT_LEVEL(reader->readInt8Checked(0, 2)); oper.expressions.emplace_back(cond); specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.283"); @@ -578,7 +552,7 @@ void CMapLoaderH3M::readVictoryLossConditions() } // Read loss conditions - auto lossCond = static_cast(reader->readUInt8()); + auto lossCond = static_cast(reader->readInt8Checked(-1, 2)); if(lossCond == ELossConditionType::LOSSSTANDARD) { mapHeader->defeatIconIndex = 3; @@ -685,9 +659,9 @@ void CMapLoaderH3M::readAllowedHeroes() if(features.levelAB) { - uint32_t placeholdersQty = reader->readUInt32(); + size_t placeholdersQty = reader->readUInt32(); - for (uint32_t i = 0; i < placeholdersQty; ++i) + for (size_t i = 0; i < placeholdersQty; ++i) { auto heroID = reader->readHero(); mapHeader->reservedCampaignHeroes.insert(heroID); @@ -700,9 +674,9 @@ void CMapLoaderH3M::readDisposedHeroes() // Reading disposed heroes (20 bytes) if(features.levelSOD) { - ui8 disp = reader->readUInt8(); + size_t disp = reader->readUInt8(); map->disposedHeroes.resize(disp); - for(int g = 0; g < disp; ++g) + for(size_t g = 0; g < disp; ++g) { map->disposedHeroes[g].heroId = reader->readHero(); map->disposedHeroes[g].portrait = reader->readHeroPortrait(); @@ -801,10 +775,10 @@ void CMapLoaderH3M::readAllowedSpellsAbilities() void CMapLoaderH3M::readRumors() { - uint32_t rumorsCount = reader->readUInt32(); + size_t rumorsCount = reader->readUInt32(); assert(rumorsCount < 1000); // sanity check - for(int it = 0; it < rumorsCount; it++) + for(size_t it = 0; it < rumorsCount; it++) { Rumor ourRumor; ourRumor.name = readBasicString(); @@ -853,7 +827,7 @@ void CMapLoaderH3M::readPredefinedHeroes() for(int yy = 0; yy < howMany; ++yy) { hero->secSkills[yy].first = reader->readSkill(); - hero->secSkills[yy].second = reader->readUInt8(); + hero->secSkills[yy].second = reader->readInt8Checked(1,3); } } @@ -864,7 +838,7 @@ void CMapLoaderH3M::readPredefinedHeroes() hero->biographyCustomTextId = readLocalizedString(TextIdentifier("heroes", heroID, "biography")); // 0xFF is default, 00 male, 01 female - hero->gender = static_cast(reader->readUInt8()); + hero->gender = static_cast(reader->readInt8Checked(-1, 1)); assert(hero->gender == EHeroGender::MALE || hero->gender == EHeroGender::FEMALE || hero->gender == EHeroGender::DEFAULT); bool hasCustomSpells = reader->readBool(); @@ -910,8 +884,8 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero) // bag artifacts // number of artifacts in hero's bag - int amount = reader->readUInt16(); - for(int i = 0; i < amount; ++i) + size_t amount = reader->readUInt16(); + for(size_t i = 0; i < amount; ++i) { loadArtifactToSlot(hero, ArtifactPosition::BACKPACK_START + static_cast(hero->artifactsInBackpack.size())); } @@ -1038,33 +1012,33 @@ void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPositi reward.heroExperience = reader->readUInt32(); reward.manaDiff = reader->readInt32(); - if(auto val = reader->readUInt8()) + if(auto val = reader->readInt8Checked(-3, 3)) reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven)); - if(auto val = reader->readUInt8()) + if(auto val = reader->readInt8Checked(-3, 3)) reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven)); reader->readResourses(reward.resources); for(int x = 0; x < GameConstants::PRIMARY_SKILLS; ++x) reward.primary.at(x) = reader->readUInt8(); - int gabn = reader->readUInt8(); //number of gained abilities - for(int oo = 0; oo < gabn; ++oo) + size_t gabn = reader->readUInt8(); //number of gained abilities + for(size_t oo = 0; oo < gabn; ++oo) { auto rId = reader->readSkill(); - auto rVal = reader->readUInt8(); + auto rVal = reader->readInt8Checked(1,3); reward.secondary[rId] = rVal; } - int gart = reader->readUInt8(); //number of gained artifacts - for(int oo = 0; oo < gart; ++oo) + size_t gart = reader->readUInt8(); //number of gained artifacts + for(size_t oo = 0; oo < gart; ++oo) reward.artifacts.push_back(reader->readArtifact()); - int gspel = reader->readUInt8(); //number of gained spells - for(int oo = 0; oo < gspel; ++oo) + size_t gspel = reader->readUInt8(); //number of gained spells + for(size_t oo = 0; oo < gspel; ++oo) reward.spells.push_back(reader->readSpell()); - int gcre = reader->readUInt8(); //number of gained creatures - for(int oo = 0; oo < gcre; ++oo) + size_t gcre = reader->readUInt8(); //number of gained creatures + for(size_t oo = 0; oo < gcre; ++oo) { auto rId = reader->readCreature(); auto rVal = reader->readUInt16(); @@ -1094,7 +1068,7 @@ CGObjectInstance * CMapLoaderH3M::readMonster(const int3 & mapPosition, const Ob //type will be set during initialization object->putStack(SlotID(0), hlp); - object->character = reader->readInt8(); + object->character = reader->readInt8Checked(0, 4); bool hasMessage = reader->readBool(); if(hasMessage) @@ -1192,17 +1166,17 @@ CGObjectInstance * CMapLoaderH3M::readWitchHut(const int3 & position, std::share CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared_ptr objectTemplate) { - enum class ScholarBonusType : uint8_t { + enum class ScholarBonusType : int8_t { + RANDOM = -1, PRIM_SKILL = 0, SECONDARY_SKILL = 1, SPELL = 2, - RANDOM = 255 }; auto * object = readGeneric(position, objectTemplate); auto * rewardable = dynamic_cast(object); - uint8_t bonusTypeRaw = reader->readUInt8(); + uint8_t bonusTypeRaw = reader->readInt8Checked(-1, 2); auto bonusType = static_cast(bonusTypeRaw); auto bonusID = reader->readUInt8(); @@ -1477,7 +1451,7 @@ CGObjectInstance * CMapLoaderH3M::readBank(const int3 & mapPosition, std::shared int32_t guardsPresetIndex = reader->readInt32(); // presence of upgraded stack: -1 = random, 0 = never, 1 = always - int8_t upgradedStackPresence = reader->readInt8(); + int8_t upgradedStackPresence = reader->readInt8Checked(-1, 1); assert(vstd::iswithin(guardsPresetIndex, -1, 4)); assert(vstd::iswithin(upgradedStackPresence, -1, 1)); @@ -1807,7 +1781,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec for(int i = 0; i < skillsCount; ++i) { object->secSkills[i].first = reader->readSkill(); - object->secSkills[i].second = reader->readUInt8(); + object->secSkills[i].second = reader->readInt8Checked(1,3); } } @@ -1815,7 +1789,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec if(hasGarison) readCreatureSet(object, 7); - object->formation = static_cast(reader->readUInt8()); + object->formation = static_cast(reader->readInt8Checked(0, 1)); assert(object->formation == EArmyFormation::LOOSE || object->formation == EArmyFormation::TIGHT); loadArtifactsOfHero(object); @@ -1828,7 +1802,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec if(hasCustomBiography) object->biographyCustomTextId = readLocalizedString(TextIdentifier("heroes", object->subID, "biography")); - object->gender = static_cast(reader->readUInt8()); + object->gender = static_cast(reader->readInt8Checked(-1, 1)); assert(object->gender == EHeroGender::MALE || object->gender == EHeroGender::FEMALE || object->gender == EHeroGender::DEFAULT); } else @@ -1938,30 +1912,12 @@ enum class ESeerHutRewardType : uint8_t CREATURE = 10, }; -enum class EQuestMission { - NONE = 0, - LEVEL = 1, - PRIMARY_SKILL = 2, - KILL_HERO = 3, - KILL_CREATURE = 4, - ARTIFACT = 5, - ARMY = 6, - RESOURCES = 7, - HERO = 8, - PLAYER = 9, - HOTA_MULTI = 10, - // end of H3 missions - KEYMASTER = 100, - HOTA_HERO_CLASS = 101, - HOTA_REACH_DATE = 102 -}; - void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven) { EQuestMission missionType = EQuestMission::NONE; if(features.levelAB) { - missionType = static_cast(readQuest(hut, position)); + missionType = readQuest(hut, position); } else { @@ -1981,7 +1937,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con if(missionType != EQuestMission::NONE) { - auto rewardType = static_cast(reader->readUInt8()); + auto rewardType = static_cast(reader->readInt8Checked(0, 10)); Rewardable::VisitInfo vinfo; auto & reward = vinfo.reward; switch(rewardType) @@ -2003,36 +1959,34 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con } case ESeerHutRewardType::MORALE: { - reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, reader->readUInt8(), BonusSourceID(idToBeGiven)); + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, reader->readInt8Checked(-3, 3), BonusSourceID(idToBeGiven)); break; } case ESeerHutRewardType::LUCK: { - reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, reader->readUInt8(), BonusSourceID(idToBeGiven)); + reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, reader->readInt8Checked(-3, 3), BonusSourceID(idToBeGiven)); break; } case ESeerHutRewardType::RESOURCES: { - auto rId = reader->readUInt8(); + auto rId = reader->readGameResID(); auto rVal = reader->readUInt32(); - assert(rId < features.resourcesCount); - reward.resources[rId] = rVal; break; } case ESeerHutRewardType::PRIMARY_SKILL: { - auto rId = reader->readUInt8(); + auto rId = reader->readPrimary(); auto rVal = reader->readUInt8(); - reward.primary.at(rId) = rVal; + reward.primary.at(rId.getNum()) = rVal; break; } case ESeerHutRewardType::SECONDARY_SKILL: { auto rId = reader->readSkill(); - auto rVal = reader->readUInt8(); + auto rVal = reader->readInt8Checked(1,3); reward.secondary[rId] = rVal; break; @@ -2071,11 +2025,11 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con } } -int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) +EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) { - auto missionId = reader->readUInt8(); + auto missionId = static_cast(reader->readInt8Checked(0, 10)); - switch(static_cast(missionId)) + switch(missionId) { case EQuestMission::NONE: return missionId; @@ -2100,8 +2054,8 @@ int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) } case EQuestMission::ARTIFACT: { - int artNumber = reader->readUInt8(); - for(int yy = 0; yy < artNumber; ++yy) + size_t artNumber = reader->readUInt8(); + for(size_t yy = 0; yy < artNumber; ++yy) { auto artid = reader->readArtifact(); guard->quest->mission.artifacts.push_back(artid); @@ -2111,9 +2065,9 @@ int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) } case EQuestMission::ARMY: { - int typeNumber = reader->readUInt8(); + size_t typeNumber = reader->readUInt8(); guard->quest->mission.creatures.resize(typeNumber); - for(int hh = 0; hh < typeNumber; ++hh) + for(size_t hh = 0; hh < typeNumber; ++hh) { guard->quest->mission.creatures[hh].type = reader->readCreature().toCreature(); guard->quest->mission.creatures[hh].count = reader->readUInt16(); @@ -2143,7 +2097,7 @@ int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) if(missionSubID == 0) { - missionId = int(EQuestMission::HOTA_HERO_CLASS); + missionId = EQuestMission::HOTA_HERO_CLASS; std::set heroClasses; reader->readBitmaskHeroClassesSized(heroClasses, false); for(auto & hc : heroClasses) @@ -2152,7 +2106,7 @@ int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) } if(missionSubID == 1) { - missionId = int(EQuestMission::HOTA_REACH_DATE); + missionId = EQuestMission::HOTA_REACH_DATE; guard->quest->mission.daysPassed = reader->readUInt32() + 1; break; } @@ -2194,7 +2148,7 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt if(hasGarrison) readCreatureSet(object, 7); - object->formation = static_cast(reader->readUInt8()); + object->formation = static_cast(reader->readInt8Checked(0, 1)); assert(object->formation == EArmyFormation::LOOSE || object->formation == EArmyFormation::TIGHT); bool hasCustomBuildings = reader->readBool(); @@ -2252,7 +2206,7 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt else event.humanAffected = true; - event.computerAffected = reader->readUInt8(); + event.computerAffected = reader->readBool(); event.firstOccurence = reader->readUInt16(); event.nextOccurence = reader->readUInt8(); diff --git a/lib/mapping/MapFormatH3M.h b/lib/mapping/MapFormatH3M.h index a0064bdca..84100c45a 100644 --- a/lib/mapping/MapFormatH3M.h +++ b/lib/mapping/MapFormatH3M.h @@ -35,6 +35,50 @@ class SpellID; class PlayerColor; class int3; +enum class EQuestMission { + NONE = 0, + LEVEL = 1, + PRIMARY_SKILL = 2, + KILL_HERO = 3, + KILL_CREATURE = 4, + ARTIFACT = 5, + ARMY = 6, + RESOURCES = 7, + HERO = 8, + PLAYER = 9, + HOTA_MULTI = 10, + // end of H3 missions + KEYMASTER = 100, + HOTA_HERO_CLASS = 101, + HOTA_REACH_DATE = 102 +}; + +enum class EVictoryConditionType : int8_t +{ + WINSTANDARD = -1, + ARTIFACT = 0, + GATHERTROOP = 1, + GATHERRESOURCE = 2, + BUILDCITY = 3, + BUILDGRAIL = 4, + BEATHERO = 5, + CAPTURECITY = 6, + BEATMONSTER = 7, + TAKEDWELLINGS = 8, + TAKEMINES = 9, + TRANSPORTITEM = 10, + HOTA_ELIMINATE_ALL_MONSTERS = 11, + HOTA_SURVIVE_FOR_DAYS = 12 +}; + +enum class ELossConditionType : int8_t +{ + LOSSSTANDARD = -1, + LOSSCASTLE = 0, + LOSSHERO = 1, + TIMEEXPIRES = 2 +}; + class DLL_LINKAGE CMapLoaderH3M : public IMapLoader { public: @@ -204,7 +248,7 @@ private: * * @param guard the quest guard where that quest should be applied to */ - int readQuest(IQuestObject * guard, const int3 & position); + EQuestMission readQuest(IQuestObject * guard, const int3 & position); void readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven); diff --git a/lib/mapping/MapReaderH3M.cpp b/lib/mapping/MapReaderH3M.cpp index 2714e19c8..d2db92dff 100644 --- a/lib/mapping/MapReaderH3M.cpp +++ b/lib/mapping/MapReaderH3M.cpp @@ -183,6 +183,13 @@ RiverId MapReaderH3M::readRiver() return result; } +PrimarySkill MapReaderH3M::readPrimary() +{ + PrimarySkill result(readUInt8()); + assert(result <= PrimarySkill::KNOWLEDGE ); + return result; +} + SecondarySkill MapReaderH3M::readSkill() { SecondarySkill result(readUInt8()); @@ -400,32 +407,35 @@ bool MapReaderH3M::readBool() return result != 0; } -ui8 MapReaderH3M::readUInt8() +int8_t MapReaderH3M::readInt8Checked(int8_t lowerLimit, int8_t upperLimit) +{ + int8_t result = readInt8(); + assert(result >= lowerLimit); + assert(result <= upperLimit); + return std::clamp(result, lowerLimit, upperLimit); +} + +uint8_t MapReaderH3M::readUInt8() { return reader->readUInt8(); } -si8 MapReaderH3M::readInt8() +int8_t MapReaderH3M::readInt8() { return reader->readInt8(); } -ui16 MapReaderH3M::readUInt16() +uint16_t MapReaderH3M::readUInt16() { return reader->readUInt16(); } -si16 MapReaderH3M::readInt16() -{ - return reader->readInt16(); -} - -ui32 MapReaderH3M::readUInt32() +uint32_t MapReaderH3M::readUInt32() { return reader->readUInt32(); } -si32 MapReaderH3M::readInt32() +int32_t MapReaderH3M::readInt32() { return reader->readInt32(); } @@ -435,9 +445,4 @@ std::string MapReaderH3M::readBaseString() return reader->readBaseString(); } -CBinaryReader & MapReaderH3M::getInternalReader() -{ - return *reader; -} - VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/MapReaderH3M.h b/lib/mapping/MapReaderH3M.h index e2b5e01b8..42b446ba5 100644 --- a/lib/mapping/MapReaderH3M.h +++ b/lib/mapping/MapReaderH3M.h @@ -40,6 +40,7 @@ public: TerrainId readTerrain(); RoadId readRoad(); RiverId readRiver(); + PrimarySkill readPrimary(); SecondarySkill readSkill(); SpellID readSpell(); SpellID readSpell32(); @@ -70,17 +71,17 @@ public: bool readBool(); - ui8 readUInt8(); - si8 readInt8(); - ui16 readUInt16(); - si16 readInt16(); - ui32 readUInt32(); - si32 readInt32(); + uint8_t readUInt8(); + int8_t readInt8(); + int8_t readInt8Checked(int8_t lowerLimit, int8_t upperLimit); + + uint16_t readUInt16(); + + uint32_t readUInt32(); + int32_t readInt32(); std::string readBaseString(); - CBinaryReader & getInternalReader(); - private: template Identifier remapIdentifier(const Identifier & identifier); diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 807090d78..02543f967 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -434,7 +434,7 @@ void CMapGenerator::addHeaderInfo() m.twoLevel = mapGenOptions.getHasTwoLevels(); m.name.appendLocalString(EMetaText::GENERAL_TXT, 740); m.description.appendRawString(getMapDescription()); - m.difficulty = 1; + m.difficulty = EMapDifficulty::NORMAL; addPlayerInfo(); m.waterMap = (mapGenOptions.getWaterContent() != EWaterContent::EWaterContent::NONE); m.banWaterContent(); From 9e09fe08e1a0ab2d80c722b6a09f46fbfa3724c4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 5 Feb 2024 21:55:48 +0200 Subject: [PATCH 78/98] Fixed duplicated hero check - was used too early, before hero type is loaded --- lib/mapObjects/CGHeroInstance.cpp | 22 ---------------------- lib/mapping/MapFormatJson.cpp | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 53bb5c701..43889275f 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1524,28 +1524,6 @@ std::string CGHeroInstance::getHeroTypeName() const void CGHeroInstance::afterAddToMap(CMap * map) { - if(ID != Obj::RANDOM_HERO) - { - auto existingHero = std::find_if(map->objects.begin(), map->objects.end(), [&](const CGObjectInstance * o) ->bool - { - return o && (o->ID == Obj::HERO || o->ID == Obj::PRISON) && o->subID == subID && o != this; - }); - - if(existingHero != map->objects.end()) - { - if(settings["session"]["editor"].Bool()) - { - logGlobal->warn("Hero is already on the map at %s", (*existingHero)->visitablePos().toString()); - } - else - { - logGlobal->error("Hero is already on the map at %s", (*existingHero)->visitablePos().toString()); - - throw std::runtime_error("Hero is already on the map"); - } - } - } - if(ID != Obj::PRISON) { map->heroesOnMap.emplace_back(this); diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 9fa8375fe..90ad01008 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -1156,6 +1156,21 @@ void CMapLoaderJson::readObjects() { return a->getObjTypeIndex() < b->getObjTypeIndex(); }); + + + std::set debugHeroesOnMap; + for (auto const & object : map->objects) + { + if(object->ID != Obj::HERO && object->ID != Obj::PRISON) + continue; + + auto * hero = dynamic_cast(object.get()); + + if (debugHeroesOnMap.count(hero->getHeroType())) + logGlobal->error("Hero is already on the map at %s", hero->visitablePos().toString()); + + debugHeroesOnMap.insert(hero->getHeroType()); + } } void CMapLoaderJson::readTranslations() From a18f9d1e8d88f62129f764c9f2c8246c6e152890 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 5 Feb 2024 21:56:06 +0200 Subject: [PATCH 79/98] Added workaround for references to old 'torosar ' ID --- config/heroes/tower.json | 1 + lib/CHeroHandler.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/config/heroes/tower.json b/config/heroes/tower.json index 0fecfc54b..4ddc15e86 100644 --- a/config/heroes/tower.json +++ b/config/heroes/tower.json @@ -70,6 +70,7 @@ "torosar": { "index": 36, + "compatibilityIdentifiers" : [ "torosar " ], "class" : "alchemist", "female": false, "spellbook": [ "magicArrow" ], diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index a882cda0b..b8ba71690 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -326,6 +326,8 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js { JsonNode classConf = node["mapObject"]; classConf["heroClass"].String() = identifier; + if (!node["compatibilityIdentifiers"].isNull()) + classConf["compatibilityIdentifiers"] = node["compatibilityIdentifiers"]; classConf.setMeta(scope); VLC->objtypeh->loadSubObject(identifier, classConf, index, heroClass->getIndex()); }); @@ -756,6 +758,9 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod objects.emplace_back(object); registerObject(scope, "hero", name, object->getIndex()); + + for(const auto & compatID : data["compatibilityIdentifiers"].Vector()) + registerObject(scope, "hero", compatID.String(), object->getIndex()); } void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) @@ -767,6 +772,8 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod objects[index] = object; registerObject(scope, "hero", name, object->getIndex()); + for(const auto & compatID : data["compatibilityIdentifiers"].Vector()) + registerObject(scope, "hero", compatID.String(), object->getIndex()); } ui32 CHeroHandler::level (TExpType experience) const From 1cecaf2bf5f8f0ef74dd78629bca5ca1d06c01d6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 5 Feb 2024 22:26:53 +0200 Subject: [PATCH 80/98] Skip unresolved identifier from list of allowed heroes/artifacts in vmap --- lib/serializer/JsonSerializeFormat.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/serializer/JsonSerializeFormat.cpp b/lib/serializer/JsonSerializeFormat.cpp index 4da52c445..bbf576a96 100644 --- a/lib/serializer/JsonSerializeFormat.cpp +++ b/lib/serializer/JsonSerializeFormat.cpp @@ -135,9 +135,16 @@ void JsonSerializeFormat::readLICPart(const JsonNode & part, const JsonSerialize { const std::string & identifier = index.String(); - const si32 rawId = decoder(identifier); - if(rawId != -1) + try + { + const si32 rawId = decoder(identifier); value.insert(rawId); + } + catch (const IdentifierResolutionException & e) + { + // downgrade exception to warning (printed as part of exception generation + // this is usually caused by loading allowed heroes / artifacts list from vmap's + } } } From a2bd3f5b465aa9dcf5d06ec3eaaecb25ab5d6bda Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Fri, 2 Feb 2024 20:32:56 +0100 Subject: [PATCH 81/98] Add more patterns to .gitignore These were needed for files created by `cmake ../vcmi && cmake --build .` --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 21b784dd2..28ba6ab2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,15 @@ /client/vcmiclient /server/vcmiserver +/launcher/.lupdate /launcher/vcmilauncher +/mapeditor/.lupdate /launcher/vcmilauncher_automoc.cpp /conan-* build/ .cache/* out/ +/.qt *.dll *.exe *.depend @@ -42,6 +45,7 @@ VCMI_VS11.opensdf .DS_Store CMakeUserPresets.json compile_commands.json +fuzzylite.pc # Visual Studio *.suo @@ -62,5 +66,8 @@ compile_commands.json /deps .vs/ +# Visual Studio Code +/.vscode/ + # CLion .idea/ From efb021333be00dfa475cb3eb30722d11846169e1 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Mon, 5 Feb 2024 20:09:18 +0100 Subject: [PATCH 82/98] Building_Linux.md: Use `cmake -S ../vcmi` instead of `cmake ../vcmi` --- docs/developers/Building_Linux.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/developers/Building_Linux.md b/docs/developers/Building_Linux.md index 4190ad49b..559527f1b 100644 --- a/docs/developers/Building_Linux.md +++ b/docs/developers/Building_Linux.md @@ -49,12 +49,14 @@ Information about building packages from the Arch User Repository (AUR) can be f # Getting the sources -VCMI is still in development. We recommend the following initial directory structure: +We recommend the following directory structure: . ├── vcmi -> contains sources and is under git control └── build -> contains build output, makefiles, object files,... +Out-of-source builds keep the local repository clean so one doesn't have to manually exclude files generated during the build from commits. + You can get latest sources with: `git clone -b develop --recursive https://github.com/vcmi/vcmi.git` @@ -65,25 +67,25 @@ You can get latest sources with: ```sh mkdir build && cd build -cmake ../vcmi +cmake -S ../vcmi ``` # Additional options that you may want to use: ## To enable debugging: -`cmake ../vcmi -D CMAKE_BUILD_TYPE=Debug` +`cmake -S ../vcmi -D CMAKE_BUILD_TYPE=Debug` **Notice**: The ../vcmi/ is not a typo, it will place makefile scripts into the build dir as the build dir is your working dir when calling CMake. ## To use ccache: -`cmake ../vcmi -D ENABLE_CCACHE:BOOL=ON` +`cmake -S ../vcmi -D ENABLE_CCACHE:BOOL=ON` ## Trigger build `cmake --build . -- -j2` (-j2 = compile with 2 threads, you can specify any value) -That will generate vcmiclient, vcmiserver, vcmilauncher as well as .so libraries in **build/bin/** directory. +That will generate vcmiclient, vcmiserver, vcmilauncher as well as .so libraries in the **build/bin/** directory. # Package building From fd17133da333219715bb991be999c1c99ab60f00 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 6 Feb 2024 00:39:05 +0200 Subject: [PATCH 83/98] Fix editor build --- lib/mapping/CMap.cpp | 8 ++++++-- mapeditor/mapsettings/generalsettings.cpp | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index c0d97a0c0..b6b520f13 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -42,8 +42,12 @@ DisposedHero::DisposedHero() : heroId(0), portrait(255) } -CMapEvent::CMapEvent() : players(0), humanAffected(0), computerAffected(0), - firstOccurence(0), nextOccurence(0) +CMapEvent::CMapEvent() + : players(0) + , humanAffected(false) + , computerAffected(false) + , firstOccurence(0) + , nextOccurence(0) { } diff --git a/mapeditor/mapsettings/generalsettings.cpp b/mapeditor/mapsettings/generalsettings.cpp index a15599e84..74f405232 100644 --- a/mapeditor/mapsettings/generalsettings.cpp +++ b/mapeditor/mapsettings/generalsettings.cpp @@ -35,23 +35,23 @@ void GeneralSettings::initialize(MapController & c) //set difficulty switch(controller->map()->difficulty) { - case 0: + case EMapDifficulty::EASY: ui->diffRadio1->setChecked(true); break; - case 1: + case EMapDifficulty::NORMAL: ui->diffRadio2->setChecked(true); break; - case 2: + case EMapDifficulty::HARD: ui->diffRadio3->setChecked(true); break; - case 3: + case EMapDifficulty::EXPERT: ui->diffRadio4->setChecked(true); break; - case 4: + case EMapDifficulty::IMPOSSIBLE: ui->diffRadio5->setChecked(true); break; }; @@ -67,11 +67,11 @@ void GeneralSettings::update() controller->map()->levelLimit = 0; //set difficulty - if(ui->diffRadio1->isChecked()) controller->map()->difficulty = 0; - if(ui->diffRadio2->isChecked()) controller->map()->difficulty = 1; - if(ui->diffRadio3->isChecked()) controller->map()->difficulty = 2; - if(ui->diffRadio4->isChecked()) controller->map()->difficulty = 3; - if(ui->diffRadio5->isChecked()) controller->map()->difficulty = 4; + if(ui->diffRadio1->isChecked()) controller->map()->difficulty = EMapDifficulty::EASY; + if(ui->diffRadio2->isChecked()) controller->map()->difficulty = EMapDifficulty::NORMAL; + if(ui->diffRadio3->isChecked()) controller->map()->difficulty = EMapDifficulty::HARD; + if(ui->diffRadio4->isChecked()) controller->map()->difficulty = EMapDifficulty::EXPERT; + if(ui->diffRadio5->isChecked()) controller->map()->difficulty = EMapDifficulty::IMPOSSIBLE; } void GeneralSettings::on_heroLevelLimitCheck_toggled(bool checked) From 77f177adc7b84a7247160185ee15ef5ae171776c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 6 Feb 2024 16:28:35 +0200 Subject: [PATCH 84/98] Track last login time for accounts --- lobby/LobbyDatabase.cpp | 9 ++++++++- lobby/LobbyDatabase.h | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lobby/LobbyDatabase.cpp b/lobby/LobbyDatabase.cpp index 3e3b29869..f4aa5d9b4 100644 --- a/lobby/LobbyDatabase.cpp +++ b/lobby/LobbyDatabase.cpp @@ -149,6 +149,12 @@ void LobbyDatabase::prepareStatements() WHERE roomID = ? )"; + static const std::string updateAccountLoginTimeText = R"( + UPDATE accounts + SET lastLoginTime = CURRENT_TIMESTAMP + WHERE accountID = ? + )"; + // SELECT FROM static const std::string getRecentMessageHistoryText = R"( @@ -263,6 +269,7 @@ void LobbyDatabase::prepareStatements() setAccountOnlineStatement = database->prepare(setAccountOnlineText); setGameRoomStatusStatement = database->prepare(setGameRoomStatusText); + updateAccountLoginTimeStatement = database->prepare(updateAccountLoginTimeText); getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText); getIdleGameRoomStatement = database->prepare(getIdleGameRoomText); @@ -382,7 +389,7 @@ void LobbyDatabase::insertAccessCookie(const std::string & accountID, const std: void LobbyDatabase::updateAccountLoginTime(const std::string & accountID) { - assert(0); + updateAccountLoginTimeStatement->executeOnce(accountID); } std::string LobbyDatabase::getAccountDisplayName(const std::string & accountID) diff --git a/lobby/LobbyDatabase.h b/lobby/LobbyDatabase.h index 02039879c..d1fbd3c60 100644 --- a/lobby/LobbyDatabase.h +++ b/lobby/LobbyDatabase.h @@ -33,7 +33,7 @@ class LobbyDatabase SQLiteStatementPtr setAccountOnlineStatement; SQLiteStatementPtr setGameRoomStatusStatement; - SQLiteStatementPtr setGameRoomPlayerLimitStatement; + SQLiteStatementPtr updateAccountLoginTimeStatement; SQLiteStatementPtr getRecentMessageHistoryStatement; SQLiteStatementPtr getIdleGameRoomStatement; From 9730b24214ff6fc3363d2ff25b8388cb699c1a47 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 6 Feb 2024 16:36:57 +0200 Subject: [PATCH 85/98] Fix typo --- lib/mapObjects/CGHeroInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 53bb5c701..b1a95c80a 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1111,7 +1111,7 @@ std::string CGHeroInstance::getClassNameTextID() const { if (isCampaignGem()) return "core.genrltxt.735"; - return type->heroClass->getNameTranslated(); + return type->heroClass->getNameTextID(); } std::string CGHeroInstance::getNameTextID() const From 18f9d29fd23224c8d421d689d112f354e34d855f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 6 Feb 2024 16:36:57 +0200 Subject: [PATCH 86/98] Fix typo --- lib/mapObjects/CGHeroInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 43889275f..d9ad418a7 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1111,7 +1111,7 @@ std::string CGHeroInstance::getClassNameTextID() const { if (isCampaignGem()) return "core.genrltxt.735"; - return type->heroClass->getNameTranslated(); + return type->heroClass->getNameTextID(); } std::string CGHeroInstance::getNameTextID() const From 23e1b38c4f69aef3dbdaa3d2dfb02072bf978248 Mon Sep 17 00:00:00 2001 From: Tytannial Date: Wed, 7 Feb 2024 08:50:40 +0800 Subject: [PATCH 87/98] Update Chinese translation --- Mods/vcmi/config/vcmi/chinese.json | 130 +++++++++++++++-------------- 1 file changed, 67 insertions(+), 63 deletions(-) diff --git a/Mods/vcmi/config/vcmi/chinese.json b/Mods/vcmi/config/vcmi/chinese.json index 85f2f85b3..d84af049f 100644 --- a/Mods/vcmi/config/vcmi/chinese.json +++ b/Mods/vcmi/config/vcmi/chinese.json @@ -20,7 +20,7 @@ "vcmi.adventureMap.playerAttacked" : "玩家遭受攻击: %s", "vcmi.adventureMap.moveCostDetails" : "移动点数 - 花费: %TURNS 轮 + %POINTS 点移动力, 剩余移动力: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "移动点数 - 花费: %POINTS 点移动力, 剩余移动力: %REMAINING", - "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "抱歉, 重放对手回合功能目前还未实现!", + "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "抱歉,重放对手行动功能目前暂未实现!", "vcmi.capitalColors.0" : "红色", "vcmi.capitalColors.1" : "蓝色", @@ -71,9 +71,9 @@ "vcmi.lobby.mapPreview" : "地图预览", "vcmi.lobby.noPreview" : "无地上部分", "vcmi.lobby.noUnderground" : "无地下部分", - "vcmi.lobby.sortDate" : "按修改时间排序地图", + "vcmi.lobby.sortDate" : "以修改时间排序地图", - "vcmi.client.errors.invalidMap" : "{无效的地图或战役}\n\n启动游戏失败!选定的地图或战役格式不正确或已损坏。原因:\n%s", + "vcmi.client.errors.invalidMap" : "{非法地图或战役}\n\n启动游戏失败,选择的地图或者战役,无效或被污染。原因:\n%s", "vcmi.client.errors.missingCampaigns" : "{找不到数据文件}\n\n没有找到战役数据文件!你可能使用了不完整或损坏的英雄无敌3数据文件,请重新安装数据文件。", "vcmi.server.errors.existingProcess" : "一个VCMI进程已经在运行,启动新进程前请结束它。", "vcmi.server.errors.modsToEnable" : "{需要启用的mod列表}", @@ -84,9 +84,9 @@ "vcmi.server.errors.unknownEntity" : "加载保存失败! 在保存的游戏中发现未知实体'%s'! 保存可能与当前安装的mod版本不兼容!", "vcmi.settingsMainWindow.generalTab.hover" : "常规", - "vcmi.settingsMainWindow.generalTab.help" : "切换到“常规”选项卡 - 设置游戏客户端呈现", + "vcmi.settingsMainWindow.generalTab.help" : "切换到“常规”选项卡 - 配置客户端常规内容", "vcmi.settingsMainWindow.battleTab.hover" : "战斗", - "vcmi.settingsMainWindow.battleTab.help" : "切换到“战斗”选项卡 - 这些设置允许配置战斗界面和相关内容", + "vcmi.settingsMainWindow.battleTab.help" : "切换到“战斗”选项卡 - 配置游戏战斗界面内容", "vcmi.settingsMainWindow.adventureTab.hover" : "冒险地图", "vcmi.settingsMainWindow.adventureTab.help" : "切换到“冒险地图”选项卡 - 冒险地图即玩家能操作英雄移动的界面", @@ -96,43 +96,43 @@ "vcmi.systemOptions.townsGroup" : "城镇画面", "vcmi.systemOptions.fullscreenBorderless.hover" : "全屏 (无边框)", - "vcmi.systemOptions.fullscreenBorderless.help" : "{全屏}\n\n选中时,VCMI将以无边框全屏模式运行。在这种模式下,游戏会使用和桌面一致的分辨率而非设置的分辨率。 ", + "vcmi.systemOptions.fullscreenBorderless.help" : "{全屏}\n\n选中时,VCMI将以无边框全屏模式运行。该模式下,游戏会始终和桌面分辨率保持一致,无视设置的分辨率。 ", "vcmi.systemOptions.fullscreenExclusive.hover" : "全屏 (独占)", - "vcmi.systemOptions.fullscreenExclusive.help" : "{全屏}\n\n选中时,VCMI将以独占全屏模式运行。在这种模式下,游戏会将显示器分辨率改变为设置值。", - "vcmi.systemOptions.resolutionButton.hover" : "分辨率", - "vcmi.systemOptions.resolutionButton.help" : "{分辨率选择}\n\n改变游戏内的分辨率,更改后需要重启游戏使其生效。", - "vcmi.systemOptions.resolutionMenu.hover" : "分辨率选择", + "vcmi.systemOptions.fullscreenExclusive.help" : "{全屏}\n\n选中时,VCMI将以独占全屏模式运行。该模式下,游戏会将显示器分辨率改变为设置值。", + "vcmi.systemOptions.resolutionButton.hover" : "分辨率: %wx%h", + "vcmi.systemOptions.resolutionButton.help" : "{分辨率选择}\n\n改变游戏内的分辨率。", + "vcmi.systemOptions.resolutionMenu.hover" : "选择分辨率", "vcmi.systemOptions.resolutionMenu.help" : "修改游戏运行时的分辨率。", - "vcmi.systemOptions.scalingButton.hover" : "界面大小: %p%", - "vcmi.systemOptions.scalingButton.help" : "{界面大小}\n\n改变界面的大小", - "vcmi.systemOptions.scalingMenu.hover" : "选择界面大小", - "vcmi.systemOptions.scalingMenu.help" : "改变游戏界面大小。", - "vcmi.systemOptions.longTouchButton.hover" : "触控间距: %d 毫秒", // Translation note: "ms" = "milliseconds" - "vcmi.systemOptions.longTouchButton.help" : "{触控间距}\n\n使用触摸屏时,触摸屏幕指定持续时间(以毫秒为单位)后将出现弹出窗口。", - "vcmi.systemOptions.longTouchMenu.hover" : "选择触控间距", - "vcmi.systemOptions.longTouchMenu.help" : "改变触控间距。", + "vcmi.systemOptions.scalingButton.hover" : "界面缩放: %p%", + "vcmi.systemOptions.scalingButton.help" : "{界面缩放}\n\n改变用户界面的缩放比例。", + "vcmi.systemOptions.scalingMenu.hover" : "选择界面缩放", + "vcmi.systemOptions.scalingMenu.help" : "改变游戏内界面缩放。", + "vcmi.systemOptions.longTouchButton.hover" : "长触延迟: %d 毫秒", // Translation note: "ms" = "milliseconds" + "vcmi.systemOptions.longTouchButton.help" : "{长触延迟}\n\n使用触摸屏时,长触屏幕一定时间后出现弹出窗口(单位:毫秒)。", + "vcmi.systemOptions.longTouchMenu.hover" : "选择长触延迟", + "vcmi.systemOptions.longTouchMenu.help" : "改变长触延迟。", "vcmi.systemOptions.longTouchMenu.entry" : "%d 毫秒", "vcmi.systemOptions.framerateButton.hover" : "显示FPS", - "vcmi.systemOptions.framerateButton.help" : "{显示FPS}\n\n打开/关闭在游戏窗口角落的FPS指示器。", + "vcmi.systemOptions.framerateButton.help" : "{显示FPS}\n\n切换在游戏窗口角落显FPS指示器。", "vcmi.systemOptions.hapticFeedbackButton.hover" : "触觉反馈", "vcmi.systemOptions.hapticFeedbackButton.help" : "{触觉反馈}\n\n切换触摸输入的触觉反馈。", "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "界面增强", - "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{界面增强}\n\n显示所有界面增强内容,如大背包和魔法书等。", - "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "增大魔法书界面", - "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{增大魔法书界面}\n\n可以在魔法书单页中显示更多的魔法,从而获得更好的视觉效果。", - "vcmi.systemOptions.audioMuteFocus.hover" : "切换窗口时静音", - "vcmi.systemOptions.audioMuteFocus.help" : "{切换窗口时静音}\n\n快速切换窗口时将静音,在工作时,切换游戏窗口不会有声音。", + "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{界面增强}\n\n显示所有界面优化增强,如背包按钮等。关闭该项以贴近经典模式。", + "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "扩展魔法书", + "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{扩展魔法书}\n\n启用更大的魔法书界面,每页展示更多魔法,但魔法书翻页特效在该模式下无法呈现。", + "vcmi.systemOptions.audioMuteFocus.hover" : "失去焦点时静音", + "vcmi.systemOptions.audioMuteFocus.help" : "{失去焦点时静音}\n\n当窗口失去焦点时静音,游戏内消息提示和新回合提示除外。", "vcmi.adventureOptions.infoBarPick.hover" : "在信息面板显示消息", - "vcmi.adventureOptions.infoBarPick.help" : "{在信息面板显示消息}\n\n来自访问地图物件的信息将显示在信息面板,而不是弹出窗口。", + "vcmi.adventureOptions.infoBarPick.help" : "{在信息面板显示消息}\n\n尽可能将来自访问地图物件的信息将显示在信息面板,而不是弹出窗口。", "vcmi.adventureOptions.numericQuantities.hover" : "生物数量显示", "vcmi.adventureOptions.numericQuantities.help" : "{生物数量显示}\n\n以数字 A-B 格式显示不准确的敌方生物数量。", - "vcmi.adventureOptions.forceMovementInfo.hover" : "在状态栏中显示移动力", - "vcmi.adventureOptions.forceMovementInfo.help" : "{在状态栏中显示移动力}\n\n不需要按ALT就可以显示移动力。", + "vcmi.adventureOptions.forceMovementInfo.hover" : "总是显示移动花费", + "vcmi.adventureOptions.forceMovementInfo.help" : "{总是显示移动花费}\n\n总是在状态栏中显示行动力花费(否则仅在按下ALT时显示)。", "vcmi.adventureOptions.showGrid.hover" : "显示网格", "vcmi.adventureOptions.showGrid.help" : "{显示网格}\n\n显示网格覆盖层,高亮冒险地图物件的边沿。", - "vcmi.adventureOptions.borderScroll.hover" : "滚动边界", - "vcmi.adventureOptions.borderScroll.help" : "{滚动边界}\n\n当光标靠近窗口边缘时滚动冒险地图。 可以通过按住 CTRL 键来禁用。", + "vcmi.adventureOptions.borderScroll.hover" : "边缘滚动", + "vcmi.adventureOptions.borderScroll.help" : "{边缘滚动}\n\n当光标靠近窗口边缘时滚动冒险地图。 可以通过按住 CTRL 键来禁用。", "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "信息面板生物管理", "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{信息面板生物管理}\n\n允许在信息面板中重新排列生物,而不是在默认组件之间循环。", "vcmi.adventureOptions.leftButtonDrag.hover" : "左键拖动地图", @@ -140,7 +140,7 @@ "vcmi.adventureOptions.smoothDragging.hover" : "平滑地图拖动", "vcmi.adventureOptions.smoothDragging.help" : "{平滑地图拖动}\n\n启用后,地图拖动会产生柔和的羽化效果。", "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "关闭淡入淡出特效", - "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{关闭淡入淡出特效}\n\n启用后,跳过物体淡出或类似特效(资源收集,登船等)。设置此项能在渲染开销重时能够加快UI的响应,尤其是在PvP对战中。当移动速度被设置为最大时忽略此项设置。", + "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{关闭淡入淡出特效}\n\n启用后,跳过物体淡出或类似特效(资源收集,登船等)。设置此项能在渲染开销重时能够加快UI的响应,尤其是在PvP对战中。当移动速度被设置为最大时忽略此项设置。", "vcmi.adventureOptions.mapScrollSpeed1.hover": "", "vcmi.adventureOptions.mapScrollSpeed5.hover": "", "vcmi.adventureOptions.mapScrollSpeed6.hover": "", @@ -148,7 +148,7 @@ "vcmi.adventureOptions.mapScrollSpeed5.help": "将地图卷动速度设置为非常快", "vcmi.adventureOptions.mapScrollSpeed6.help": "将地图卷动速度设置为即刻。", "vcmi.adventureOptions.hideBackground.hover" : "隐藏背景", - "vcmi.adventureOptions.hideBackground.help" : "{隐藏背景}\n\n将冒险地图背景隐藏并使用一个纹理替代。", + "vcmi.adventureOptions.hideBackground.help" : "{隐藏背景}\n\n隐藏冒险地图背景,以显示贴图代替。", "vcmi.battleOptions.queueSizeLabel.hover": "回合顺序指示器", "vcmi.battleOptions.queueSizeNoneButton.hover": "关闭", @@ -173,6 +173,8 @@ "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{显示英雄统计数据窗口}\n\n永久切换并显示主要统计数据和法术点的英雄统计数据窗口。", "vcmi.battleOptions.skipBattleIntroMusic.hover": "跳过战斗开始音乐", "vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过战斗开始音乐}\n\n战斗开始音乐播放期间,你也能够进行操作。", + "vcmi.battleOptions.endWithAutocombat.hover": "结束战斗", + "vcmi.battleOptions.endWithAutocombat.help": "{结束战斗}\n\n以自动战斗立即结束剩余战斗过程", "vcmi.adventureMap.revisitObject.hover" : "重新访问", "vcmi.adventureMap.revisitObject.help" : "{重新访问}\n\n让当前英雄重新访问地图建筑或城镇。", @@ -188,21 +190,22 @@ "vcmi.battleWindow.damageEstimation.damage.1" : "%d 伤害", "vcmi.battleWindow.damageEstimation.kills" : "%d 将被消灭", "vcmi.battleWindow.damageEstimation.kills.1" : "%d 将被消灭", - "vcmi.battleWindow.killed" : "被击杀", - "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s 被精准射击击杀!", - "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s 被精准射击击杀!", - "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s 被精准射击击杀!", + "vcmi.battleWindow.killed" : "已消灭", + "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s 死于精准射击", + "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s 死于精准射击", + "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s 死于精准射击", + "vcmi.battleWindow.endWithAutocombat" : "您确定想以自动战斗立即结束吗?", "vcmi.battleResultsWindow.applyResultsLabel" : "接受战斗结果", "vcmi.tutorialWindow.title" : "触摸屏介绍", - "vcmi.tutorialWindow.decription.RightClick" : "触摸并按住要右键单击的元素。 触摸可用区域以关闭。", - "vcmi.tutorialWindow.decription.MapPanning" : "用一根手指触摸并拖动来移动地图。", - "vcmi.tutorialWindow.decription.MapZooming" : "用两根手指捏合可更改地图缩放比例。", + "vcmi.tutorialWindow.decription.RightClick" : "长按要右键单击的元素。 触摸其他区域以关闭。", + "vcmi.tutorialWindow.decription.MapPanning" : "单指拖拽以移动地图。", + "vcmi.tutorialWindow.decription.MapZooming" : "两指开合更改地图缩放比例。", "vcmi.tutorialWindow.decription.RadialWheel" : "滑动可打开径向轮以执行各种操作,例如生物/英雄管理和城镇排序。", "vcmi.tutorialWindow.decription.BattleDirection" : "要从特定方向攻击,请向要进行攻击的方向滑动。", - "vcmi.tutorialWindow.decription.BattleDirectionAbort" : "如果手指距离足够远,可以取消攻击方向手势。", - "vcmi.tutorialWindow.decription.AbortSpell" : "触摸并按住可取消魔法。", + "vcmi.tutorialWindow.decription.BattleDirectionAbort" : "将长触状态的手指拉远足够距离,可以取消攻击方向手势。", + "vcmi.tutorialWindow.decription.AbortSpell" : "长触以取消魔法。", "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "显示可招募生物", "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{显示可招募生物}\n\n在城镇摘要(城镇屏幕的左下角)中显示可招募的生物数量,而不是增长。", @@ -221,7 +224,7 @@ "vcmi.townHall.greetingDefence" : "在%s中稍待片刻,富有战斗经验的战士会教你防御技巧(防御力+1)。", "vcmi.townHall.hasNotProduced" : "本周%s并没有产生什么资源。", "vcmi.townHall.hasProduced" : "本周%s产生了%d个%s。", - "vcmi.townHall.greetingCustomBonus" : "当你的英雄访问%s 时,这个神奇的建筑使你的英雄 +%d %s%s。", + "vcmi.townHall.greetingCustomBonus" : "%s 给予英雄 +%d %s%s。", "vcmi.townHall.greetingCustomUntil" : "直到下一场战斗。", "vcmi.townHall.greetingInTownMagicWell" : "%s使你的魔法值恢复到最大值。", @@ -242,7 +245,7 @@ "vcmi.creatureWindow.showBonuses.help" : "显示指挥官的所有属性增益", "vcmi.creatureWindow.showSkills.hover" : "技能视图", "vcmi.creatureWindow.showSkills.help" : "显示指挥官的所有学习的技能", - "vcmi.creatureWindow.returnArtifact.hover" : "交换宝物", + "vcmi.creatureWindow.returnArtifact.hover" : "返还宝物", "vcmi.creatureWindow.returnArtifact.help" : "点击这个按钮将宝物反还到英雄的背包里", "vcmi.questLog.hideComplete.hover" : "隐藏完成任务", @@ -332,6 +335,7 @@ "vcmi.map.victoryCondition.collectArtifacts.message" : "获得所有三件宝物", "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "祝贺你!你取得了天使联盟且消灭了所有敌人,取得了胜利!", "vcmi.map.victoryCondition.angelicAlliance.message" : "击败所有敌人并取得天使联盟", + "vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "功亏一篑,你已失去了天使联盟的一个组件。彻底的失败。", // few strings from WoG used by vcmi "vcmi.stackExperience.description" : "» 经 验 获 得 明 细 «\n\n生物类型 ................... : %s\n经验等级 ................. : %s (%i)\n经验点数 ............... : %i\n下一个等级所需经验 .. : %i\n每次战斗最大获得经验 ... : %i%% (%i)\n获得经验的生物数量 .... : %i\n最大招募数量\n不会丢失经验升级 .... : %i\n经验倍数 ........... : %.2f\n升级倍数 .............. : %.2f\n10级后经验值 ........ : %i\n最大招募数量下\n 升级到10级所需经验数量: %i", @@ -386,9 +390,9 @@ "core.bonus.ENCHANTED.name": "法术加持", "core.bonus.ENCHANTED.description": "永久处于${subtype.spell}影响", "core.bonus.ENEMY_ATTACK_REDUCTION.name": "忽略攻击 (${val}%)", - "core.bonus.ENEMY_ATTACK_REDUCTION.description": "当被攻击时,攻击方${val}%的攻击力将被无视。", + "core.bonus.ENEMY_ATTACK_REDUCTION.description": "被攻击时,进攻方${val}%的攻击力将被无视。", "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "忽略防御 (${val}%)", - "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "当攻击时,目标生物${val}%的防御力将被无视。", + "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "发动攻击时,防御方${val}%的防御力将被无视。", "core.bonus.FIRE_IMMUNITY.name": "火系免疫", "core.bonus.FIRE_IMMUNITY.description": "免疫所有火系魔法。", "core.bonus.FIRE_SHIELD.name": "烈火神盾 (${val}%)", @@ -399,9 +403,9 @@ "core.bonus.FEAR.description": "使得敌方一只部队恐惧", "core.bonus.FEARLESS.name": "无惧", "core.bonus.FEARLESS.description": "免疫恐惧特质", - "core.bonus.FEROCITY.name": "残暴", - "core.bonus.FEROCITY.description": "如果杀死一个生物,额外攻击${val}次", - "core.bonus.FLYING.name": "飞行兵种", + "core.bonus.FEROCITY.name": "凶猛追击", + "core.bonus.FEROCITY.description": "杀死任意生物后额外攻击${val}次", + "core.bonus.FLYING.name": "飞行能力", "core.bonus.FLYING.description": "以飞行的方式移动(无视障碍)", "core.bonus.FREE_SHOOTING.name": "近身射击", "core.bonus.FREE_SHOOTING.description": "能在近战范围内进行射击", @@ -411,52 +415,52 @@ "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "减少从远程和近战中遭受的物理伤害", "core.bonus.HATE.name": "${subtype.creature}的死敌", "core.bonus.HATE.description": "对${subtype.creature}造成额外${val}%伤害", - "core.bonus.HEALER.name": "治疗", + "core.bonus.HEALER.name": "治疗者", "core.bonus.HEALER.description": "可以治疗友军单位", "core.bonus.HP_REGENERATION.name": "再生", "core.bonus.HP_REGENERATION.description": "每回合恢复${val}点生命值", - "core.bonus.JOUSTING.name": "冲锋", + "core.bonus.JOUSTING.name": "勇士冲锋", "core.bonus.JOUSTING.description": "每移动一格 +${val}%伤害", - "core.bonus.KING.name": "顶级怪物", + "core.bonus.KING.name": "王牌", "core.bonus.KING.description": "受${val}级或更高级屠戮成性影响", "core.bonus.LEVEL_SPELL_IMMUNITY.name": "免疫1-${val}级魔法", "core.bonus.LEVEL_SPELL_IMMUNITY.description": "免疫1-${val}级的魔法", - "core.bonus.LIMITED_SHOOTING_RANGE.name" : "受限射击距离", - "core.bonus.LIMITED_SHOOTING_RANGE.description" : "无法以${val}格外的单位为射击目标", + "core.bonus.LIMITED_SHOOTING_RANGE.name": "射程限制", + "core.bonus.LIMITED_SHOOTING_RANGE.description": "无法瞄准${val}格以外的单位", "core.bonus.LIFE_DRAIN.name": "吸取生命 (${val}%)", "core.bonus.LIFE_DRAIN.description": "吸取${val}%伤害回复自身", - "core.bonus.MANA_CHANNELING.name": "魔法虹吸${val}%", + "core.bonus.MANA_CHANNELING.name": "法力虹吸${val}%", "core.bonus.MANA_CHANNELING.description": "使你的英雄有${val}%几率获得敌人施法的魔法值", - "core.bonus.MANA_DRAIN.name": "吸取魔力", + "core.bonus.MANA_DRAIN.name": "吸取法力", "core.bonus.MANA_DRAIN.description": "每回合吸取${val}魔法值", "core.bonus.MAGIC_MIRROR.name": "魔法神镜 (${val}%)", "core.bonus.MAGIC_MIRROR.description": "${val}%几率将进攻性魔法导向一个敌人单位", "core.bonus.MAGIC_RESISTANCE.name": "魔法抵抗 (${val}%)", "core.bonus.MAGIC_RESISTANCE.description": "${val}%几率抵抗敌人的魔法", - "core.bonus.MIND_IMMUNITY.name": "免疫心智", - "core.bonus.MIND_IMMUNITY.description": "不受心智魔法的影响", - "core.bonus.NO_DISTANCE_PENALTY.name": "无视距离惩罚", - "core.bonus.NO_DISTANCE_PENALTY.description": "任意距离均造成全额伤害", + "core.bonus.MIND_IMMUNITY.name": "免疫心智魔法", + "core.bonus.MIND_IMMUNITY.description": "不受心智相关的魔法影响", + "core.bonus.NO_DISTANCE_PENALTY.name": "无视射程惩罚", + "core.bonus.NO_DISTANCE_PENALTY.description": "任意射程造成全额伤害", "core.bonus.NO_MELEE_PENALTY.name": "无近战惩罚", "core.bonus.NO_MELEE_PENALTY.description": "该生物没有近战伤害惩罚", "core.bonus.NO_MORALE.name": "无士气", "core.bonus.NO_MORALE.description": "生物不受士气影响", - "core.bonus.NO_WALL_PENALTY.name": "无城墙影响", - "core.bonus.NO_WALL_PENALTY.description": "攻城战中不被城墙阻挡造成全额伤害", + "core.bonus.NO_WALL_PENALTY.name": "无城墙惩罚", + "core.bonus.NO_WALL_PENALTY.description": "攻城战中无视城墙阻挡,造成全额伤害", "core.bonus.NON_LIVING.name": "无生命", "core.bonus.NON_LIVING.description": "免疫大多数的效果", "core.bonus.RANDOM_SPELLCASTER.name": "随机施法", "core.bonus.RANDOM_SPELLCASTER.description": "可以施放随机魔法", "core.bonus.RANGED_RETALIATION.name": "远程反击", "core.bonus.RANGED_RETALIATION.description": "可以对远程攻击进行反击", - "core.bonus.RECEPTIVE.name": "接受", + "core.bonus.RECEPTIVE.name": "接纳", "core.bonus.RECEPTIVE.description": "不会免疫有益魔法", "core.bonus.REBIRTH.name": "复生 (${val}%)", "core.bonus.REBIRTH.description": "当整支部队死亡后${val}%会复活", "core.bonus.RETURN_AFTER_STRIKE.name": "攻击后返回", "core.bonus.RETURN_AFTER_STRIKE.description": "近战攻击后回到初始位置", "core.bonus.REVENGE.name": "复仇", - "core.bonus.REVENGE.description": "造成额外伤害,其数量基于攻击者在战斗中损失的生命值", + "core.bonus.REVENGE.description": "根据攻击者在战斗中失去的生命值造成额外伤害", "core.bonus.SHOOTER.name": "远程攻击", "core.bonus.SHOOTER.description": "生物可以射击", "core.bonus.SHOOTS_ALL_ADJACENT.name": "范围远程攻击", @@ -488,7 +492,7 @@ "core.bonus.TRANSMUTATION.name": "变形术", "core.bonus.TRANSMUTATION.description": "${val}%机会将被攻击单位变成其他生物", "core.bonus.UNDEAD.name": "不死生物", - "core.bonus.UNDEAD.description": "该生物属于丧尸", + "core.bonus.UNDEAD.description": "该生物属于不死生物", "core.bonus.UNLIMITED_RETALIATIONS.name": "无限反击", "core.bonus.UNLIMITED_RETALIATIONS.description": "每回合可以无限反击敌人", "core.bonus.WATER_IMMUNITY.name": "水系免疫", From a6cfacc7e3dd4eb826b14e8117cfa2c113f963e6 Mon Sep 17 00:00:00 2001 From: Tytannial Date: Wed, 7 Feb 2024 09:01:55 +0800 Subject: [PATCH 88/98] Update Chinese Launcher translation --- launcher/translation/chinese.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/launcher/translation/chinese.ts b/launcher/translation/chinese.ts index 3eec1439d..7714e6a1a 100644 --- a/launcher/translation/chinese.ts +++ b/launcher/translation/chinese.ts @@ -16,7 +16,7 @@ VCMI on Github - 访问VCMI的GUTHUB + 访问VCMI的Github @@ -574,7 +574,7 @@ Install successfully downloaded? Renderer - + 渲染器 @@ -852,7 +852,7 @@ Heroes® of Might and Magic® III HD is currently not supported! VCMI on Github - 访问VCMI的GUTHUB + 访问VCMI的Github @@ -930,12 +930,12 @@ Heroes® of Might and Magic® III HD is currently not supported! Heroes III installation found! - + 英雄无敌3安装目录已找到! Copy data to VCMI folder? - + 复制数据到VCMI文件夹吗? From fd9810adf4241765479700425965537cdacd3b6e Mon Sep 17 00:00:00 2001 From: Dydzio Date: Wed, 7 Feb 2024 19:27:02 +0100 Subject: [PATCH 89/98] Working SoD version of fly --- config/gameConfig.json | 4 +++- lib/GameSettings.cpp | 1 + lib/GameSettings.h | 1 + lib/pathfinder/CPathfinder.cpp | 2 +- lib/pathfinder/PathfinderOptions.cpp | 2 +- lib/pathfinder/PathfinderOptions.h | 4 +++- lib/pathfinder/PathfindingRules.cpp | 9 +++------ server/CGameHandler.cpp | 2 +- 8 files changed, 14 insertions(+), 11 deletions(-) diff --git a/config/gameConfig.json b/config/gameConfig.json index b7d7bcd91..5c13bb56b 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -384,7 +384,9 @@ // if enabled, pathfinder will take use of one-way monoliths with multiple exits. "useMonolithOneWayRandom" : false, // if enabled and hero has whirlpool protection effect, pathfinder will take use of whirpools - "useWhirlpool" : true + "useWhirlpool" : true, + // if enabled flying will work like in original game, otherwise nerf similar to HotA flying is applied + "originalFlyRules" : false }, "bonuses" : diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 1996fb62b..3bfa34b2b 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -102,6 +102,7 @@ void GameSettings::load(const JsonNode & input) {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" }, {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom" }, {EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" }, + {EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" }, {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, }; diff --git a/lib/GameSettings.h b/lib/GameSettings.h index 4a5f5256d..65265147f 100644 --- a/lib/GameSettings.h +++ b/lib/GameSettings.h @@ -66,6 +66,7 @@ enum class EGameSettings PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, PATHFINDER_USE_WHIRLPOOL, + PATHFINDER_ORIGINAL_FLY_RULES, TOWNS_BUILDINGS_PER_TURN_CAP, TOWNS_STARTING_DWELLING_CHANCES, COMBAT_ONE_HEX_TRIGGERS_OBSTACLES, diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp index 40fa2f650..4dcf8c5ed 100644 --- a/lib/pathfinder/CPathfinder.cpp +++ b/lib/pathfinder/CPathfinder.cpp @@ -483,7 +483,7 @@ bool CPathfinderHelper::passOneTurnLimitCheck(const PathNodeInfo & source) const return false; if(source.node->layer == EPathfindingLayer::AIR) { - return options.originalMovementRules && source.node->accessible == EPathAccessibility::ACCESSIBLE; + return options.originalFlyRules && source.node->accessible == EPathAccessibility::ACCESSIBLE; } return true; diff --git a/lib/pathfinder/PathfinderOptions.cpp b/lib/pathfinder/PathfinderOptions.cpp index cf62a4d75..4b16b63f3 100644 --- a/lib/pathfinder/PathfinderOptions.cpp +++ b/lib/pathfinder/PathfinderOptions.cpp @@ -27,10 +27,10 @@ PathfinderOptions::PathfinderOptions() , useTeleportOneWay(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE)) , useTeleportOneWayRandom(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM)) , useTeleportWhirlpool(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_USE_WHIRLPOOL)) + , originalFlyRules(VLC->settings()->getBoolean(EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES)) , useCastleGate(false) , lightweightFlyingMode(false) , oneTurnSpecialLayersLimit(true) - , originalMovementRules(true) , turnLimit(std::numeric_limits::max()) , canUseCast(false) { diff --git a/lib/pathfinder/PathfinderOptions.h b/lib/pathfinder/PathfinderOptions.h index 4a1d4b985..62dd8c92b 100644 --- a/lib/pathfinder/PathfinderOptions.h +++ b/lib/pathfinder/PathfinderOptions.h @@ -67,7 +67,9 @@ struct DLL_LINKAGE PathfinderOptions /// - Option should also allow same tile land <-> air layer transitions. /// Current implementation only allow go into (from) air layer only to neighbour tiles. /// I find it's reasonable limitation, but it's will make some movements more expensive than in H3. - bool originalMovementRules; + /// Further work can also be done to mimic SoD quirks if needed + /// (such as picking unoptimal paths on purpose when targeting guards or being interrupted on guarded resource tile when picking it during diagonal u-turn) + bool originalFlyRules; /// Max number of turns to compute. Default = infinite uint8_t turnLimit; diff --git a/lib/pathfinder/PathfindingRules.cpp b/lib/pathfinder/PathfindingRules.cpp index d22bf551b..ca8e3df46 100644 --- a/lib/pathfinder/PathfindingRules.cpp +++ b/lib/pathfinder/PathfindingRules.cpp @@ -301,7 +301,7 @@ PathfinderBlockingRule::BlockingReason MovementToDestinationRule::getBlockingRea if(source.guarded) { - if(!(pathfinderConfig->options.originalMovementRules && source.node->layer == EPathfindingLayer::AIR) + if(!(pathfinderConfig->options.originalFlyRules && source.node->layer == EPathfindingLayer::AIR) && !pathfinderConfig->options.ignoreGuards && (!destination.isGuardianTile || pathfinderHelper->getGuardiansCount(source.coord) > 1)) // Can step into tile of guard { @@ -386,11 +386,8 @@ void LayerTransitionRule::process( break; case EPathfindingLayer::AIR: - if(pathfinderConfig->options.originalMovementRules) + if(pathfinderConfig->options.originalFlyRules) { - if(destination.coord.x == 2 && destination.coord.y == 35) - logGlobal->error(source.node->coord.toString() + std::string(" Layer: ") + std::to_string(destination.node->layer) + std::string(" Accessibility: ") + std::to_string((int)source.node->accessible)); - if(source.node->accessible != EPathAccessibility::ACCESSIBLE && source.node->accessible != EPathAccessibility::VISITABLE && destination.node->accessible != EPathAccessibility::VISITABLE && @@ -398,7 +395,7 @@ void LayerTransitionRule::process( { if(destination.node->accessible == EPathAccessibility::BLOCKVIS) { - if(source.nodeObject || source.tile->blocked) + if(source.nodeObject || (source.tile->blocked && destination.tile->blocked)) { destination.blocked = true; } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 789c0682a..50bbf2f73 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1195,7 +1195,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo if (leavingTile == LEAVING_TILE) leaveTile(); - if (isInTheMap(guardPos)) + if (lookForGuards == CHECK_FOR_GUARDS && isInTheMap(guardPos)) tmh.attackedFrom = std::make_optional(guardPos); tmh.result = result; From c8b6d6a684e0b3b9721f5eb8788c02ade3b8c8a6 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 7 Feb 2024 21:40:56 +0100 Subject: [PATCH 90/98] add extra button for campaigns --- client/lobby/CBonusSelection.cpp | 15 +++++++++++++-- client/lobby/CBonusSelection.h | 4 ++++ client/lobby/OptionsTabBase.cpp | 9 ++++++++- client/lobby/OptionsTabBase.h | 2 +- config/widgets/extraOptionsTab.json | 7 +++++++ 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 0939e776b..21de1f104 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -15,6 +15,7 @@ #include #include "CSelectionBase.h" +#include "ExtraOptionsTab.h" #include "../CGameInfo.h" #include "../CMusicHandler.h" @@ -103,8 +104,8 @@ CBonusSelection::CBonusSelection() if(getCampaign()->playerSelectedDifficulty()) { - buttonDifficultyLeft = std::make_shared(Point(694, 508), AnimationPath::builtin("SCNRBLF.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this)); - buttonDifficultyRight = std::make_shared(Point(738, 508), AnimationPath::builtin("SCNRBRT.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this)); + buttonDifficultyLeft = std::make_shared(settings["general"]["enableUiEnhancements"].Bool() ? Point(693, 470) : Point(694, 508), AnimationPath::builtin("SCNRBLF.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this)); + buttonDifficultyRight = std::make_shared(settings["general"]["enableUiEnhancements"].Bool() ? Point(739, 470) : Point(738, 508), AnimationPath::builtin("SCNRBRT.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this)); } for(auto scenarioID : getCampaign()->allScenarios()) @@ -117,6 +118,16 @@ CBonusSelection::CBonusSelection() if (!getCampaign()->getMusic().empty()) CCS->musich->playMusic( getCampaign()->getMusic(), true, false); + + if(settings["general"]["enableUiEnhancements"].Bool()) + { + tabExtraOptions = std::make_shared(); + tabExtraOptions->recActions = UPDATE | SHOWALL | LCLICK | RCLICK_POPUP; + tabExtraOptions->recreate(true); + tabExtraOptions->setEnabled(false); + buttonExtraOptions = std::make_shared(Point(642, 509), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], [this]{ tabExtraOptions->setEnabled(!tabExtraOptions->isActive()); GH.windows().totalRedraw(); }, EShortcut::NONE); + buttonExtraOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.extraOptions.hover"), FONT_SMALL, Colors::WHITE); + } } void CBonusSelection::createBonusesIcons() diff --git a/client/lobby/CBonusSelection.h b/client/lobby/CBonusSelection.h index 394c3b81b..989e459b3 100644 --- a/client/lobby/CBonusSelection.h +++ b/client/lobby/CBonusSelection.h @@ -27,6 +27,7 @@ class CAnimImage; class CLabel; class CFlagBox; class ISelectionScreenInfo; +class ExtraOptionsTab; /// Campaign screen where you can choose one out of three starting bonuses class CBonusSelection : public CWindowObject @@ -82,4 +83,7 @@ public: std::shared_ptr buttonDifficultyLeft; std::shared_ptr buttonDifficultyRight; std::shared_ptr iconsMapSizes; + + std::shared_ptr tabExtraOptions; + std::shared_ptr buttonExtraOptions; }; diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index a89943dc8..bb150efe7 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -12,6 +12,7 @@ #include "CSelectionBase.h" #include "../widgets/ComboBox.h" +#include "../widgets/Images.h" #include "../widgets/Slider.h" #include "../widgets/TextControls.h" #include "../CServerHandler.h" @@ -295,7 +296,7 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath) } } -void OptionsTabBase::recreate() +void OptionsTabBase::recreate(bool campaign) { auto const & generateSimturnsDurationText = [](int days) -> std::string { @@ -417,4 +418,10 @@ void OptionsTabBase::recreate() buttonUnlimitedReplay->setSelectedSilent(SEL->getStartInfo()->extraOptionsInfo.unlimitedReplay); buttonUnlimitedReplay->block(SEL->screenType == ESelectionScreen::loadGame); } + + if(auto textureCampaignOverdraw = widget("textureCampaignOverdraw")) + { + if(!campaign) + textureCampaignOverdraw->disable(); + } } diff --git a/client/lobby/OptionsTabBase.h b/client/lobby/OptionsTabBase.h index dc7bf63f2..15c6372c9 100644 --- a/client/lobby/OptionsTabBase.h +++ b/client/lobby/OptionsTabBase.h @@ -28,5 +28,5 @@ class OptionsTabBase : public InterfaceObjectConfigurable public: OptionsTabBase(const JsonPath & configPath); - void recreate(); + void recreate(bool campaign = false); }; diff --git a/config/widgets/extraOptionsTab.json b/config/widgets/extraOptionsTab.json index d8a333b9c..91b41bced 100644 --- a/config/widgets/extraOptionsTab.json +++ b/config/widgets/extraOptionsTab.json @@ -9,6 +9,13 @@ "image": "ADVOPTBK", "position": {"x": 0, "y": 6} }, + { + "name": "textureCampaignOverdraw", + "type": "texture", + "color" : "blue", + "image": "DIBOXBCK", + "rect": {"x": 391, "y": 14, "w": 82, "h": 569} + }, { "name": "labelTitle", "type": "label", From 4e8152f2688dd4d55e0b0839c07e48007808454b Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 7 Feb 2024 22:04:50 +0100 Subject: [PATCH 91/98] reorder gui --- client/lobby/CBonusSelection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 21de1f104..1805efc22 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -87,7 +87,7 @@ CBonusSelection::CBonusSelection() labelMapDescription = std::make_shared(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]); mapDescription = std::make_shared("", Rect(480, 278, 292, 108), 1); - labelChooseBonus = std::make_shared(511, 432, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[71]); + labelChooseBonus = std::make_shared(475, 432, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[71]); groupBonuses = std::make_shared(std::bind(&IServerAPI::setCampaignBonus, CSH, _1)); flagbox = std::make_shared(Rect(486, 407, 335, 23)); @@ -95,17 +95,17 @@ CBonusSelection::CBonusSelection() std::vector difficulty; std::string difficultyString = CGI->generaltexth->allTexts[492]; boost::split(difficulty, difficultyString, boost::is_any_of(" ")); - labelDifficulty = std::make_shared(689, 432, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, difficulty.back()); + labelDifficulty = std::make_shared(724, settings["general"]["enableUiEnhancements"].Bool() ? 457 : 432, FONT_MEDIUM, ETextAlignment::TOPCENTER, Colors::WHITE, difficulty.back()); for(size_t b = 0; b < difficultyIcons.size(); ++b) { - difficultyIcons[b] = std::make_shared(AnimationPath::builtinTODO("GSPBUT" + std::to_string(b + 3) + ".DEF"), 0, 0, 709, 455); + difficultyIcons[b] = std::make_shared(AnimationPath::builtinTODO("GSPBUT" + std::to_string(b + 3) + ".DEF"), 0, 0, 709, settings["general"]["enableUiEnhancements"].Bool() ? 480 : 455); } if(getCampaign()->playerSelectedDifficulty()) { - buttonDifficultyLeft = std::make_shared(settings["general"]["enableUiEnhancements"].Bool() ? Point(693, 470) : Point(694, 508), AnimationPath::builtin("SCNRBLF.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this)); - buttonDifficultyRight = std::make_shared(settings["general"]["enableUiEnhancements"].Bool() ? Point(739, 470) : Point(738, 508), AnimationPath::builtin("SCNRBRT.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this)); + buttonDifficultyLeft = std::make_shared(settings["general"]["enableUiEnhancements"].Bool() ? Point(693, 495) : Point(694, 508), AnimationPath::builtin("SCNRBLF.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this)); + buttonDifficultyRight = std::make_shared(settings["general"]["enableUiEnhancements"].Bool() ? Point(739, 495) : Point(738, 508), AnimationPath::builtin("SCNRBRT.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this)); } for(auto scenarioID : getCampaign()->allScenarios()) @@ -125,7 +125,7 @@ CBonusSelection::CBonusSelection() tabExtraOptions->recActions = UPDATE | SHOWALL | LCLICK | RCLICK_POPUP; tabExtraOptions->recreate(true); tabExtraOptions->setEnabled(false); - buttonExtraOptions = std::make_shared(Point(642, 509), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], [this]{ tabExtraOptions->setEnabled(!tabExtraOptions->isActive()); GH.windows().totalRedraw(); }, EShortcut::NONE); + buttonExtraOptions = std::make_shared(Point(643, 431), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], [this]{ tabExtraOptions->setEnabled(!tabExtraOptions->isActive()); GH.windows().totalRedraw(); }, EShortcut::NONE); buttonExtraOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.extraOptions.hover"), FONT_SMALL, Colors::WHITE); } } From 51756f988913a9a82ba5e1e3f22ffdc55c5ff65c Mon Sep 17 00:00:00 2001 From: Asirome <66029412+macomarivas@users.noreply.github.com> Date: Thu, 8 Feb 2024 10:45:24 +0100 Subject: [PATCH 92/98] Updated spanish.json translation --- Mods/vcmi/config/vcmi/spanish.json | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Mods/vcmi/config/vcmi/spanish.json b/Mods/vcmi/config/vcmi/spanish.json index ab1f02eb7..12291a2cc 100644 --- a/Mods/vcmi/config/vcmi/spanish.json +++ b/Mods/vcmi/config/vcmi/spanish.json @@ -20,6 +20,7 @@ "vcmi.adventureMap.playerAttacked" : "El jugador ha sido atacado: %s", "vcmi.adventureMap.moveCostDetails" : "Puntos de movimiento - Coste: %TURNS turnos + %POINTS puntos, Puntos restantes: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "Puntos de movimiento - Coste: %POINTS puntos, Puntos restantes: %REMAINING", + "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Disculpe, la repetición del turno del oponente aún no está implementada.", "vcmi.capitalColors.0" : "Rojo", "vcmi.capitalColors.1" : "Azul", @@ -70,7 +71,9 @@ "vcmi.lobby.mapPreview" : "Vista previa del mapa", "vcmi.lobby.noPreview" : "sin vista previa", "vcmi.lobby.noUnderground" : "sin subterráneo", + "vcmi.lobby.sortDate" : "Ordena los mapas por la fecha de modificación", + "vcmi.client.errors.invalidMap" : "{Mapa o Campaña invalido}\n\n¡No se pudo iniciar el juego! El mapa o la campaña seleccionados pueden no ser válidos o estar dañados. Motivo:\n%s", "vcmi.client.errors.missingCampaigns" : "{Archivos de datos faltantes}\n\n¡No se encontraron los archivos de datos de las campañas! Quizás estés utilizando archivos de datos incompletos o dañados de Heroes 3. Por favor, reinstala los datos del juego.", "vcmi.server.errors.existingProcess" : "Otro servidor VCMI está en ejecución. Por favor, termínalo antes de comenzar un nuevo juego.", "vcmi.server.errors.modsToEnable" : "{Se requieren los siguientes mods}", @@ -144,6 +147,8 @@ "vcmi.adventureOptions.mapScrollSpeed1.help": "Establece la velocidad de desplazamiento del mapa como muy lenta", "vcmi.adventureOptions.mapScrollSpeed5.help": "Establece la velocidad de desplazamiento del mapa como muy rápida", "vcmi.adventureOptions.mapScrollSpeed6.help": "Establece la velocidad de desplazamiento del mapa como instantánea.", + "vcmi.adventureOptions.hideBackground.hover" : "Ocultar fondo", + "vcmi.adventureOptions.hideBackground.help" : "{Ocultar fondo}\n\nOculta el mapa de aventuras en el fondo y muestra una textura en su lugar..", "vcmi.battleOptions.queueSizeLabel.hover": "Mostrar orden de turno de criaturas", "vcmi.battleOptions.queueSizeNoneButton.hover": "APAGADO", @@ -168,6 +173,8 @@ "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Mostrar ventanas de estadísticas de héroes}\n\nAlternar permanentemente las ventanas de estadísticas de héroes que muestran estadísticas primarias y puntos de hechizo.", "vcmi.battleOptions.skipBattleIntroMusic.hover": "Omitir música de introducción", "vcmi.battleOptions.skipBattleIntroMusic.help": "{Omitir música de introducción}\n\nPermitir acciones durante la música de introducción que se reproduce al comienzo de cada batalla.", + "vcmi.battleOptions.endWithAutocombat.hover": "Finaliza la batalla", + "vcmi.battleOptions.endWithAutocombat.help": "{Finaliza la batalla}\n\nAutomatiza la batalla y la finaliza al instante", "vcmi.adventureMap.revisitObject.hover" : "Revisitar objeto", "vcmi.adventureMap.revisitObject.help" : "{Revisitar objeto}\n\nSi un héroe se encuentra actualmente en un objeto del mapa, puede volver a visitar la ubicación.", @@ -183,6 +190,11 @@ "vcmi.battleWindow.damageEstimation.damage.1" : "%d daño", "vcmi.battleWindow.damageEstimation.kills" : "%d perecerán", "vcmi.battleWindow.damageEstimation.kills.1" : "%d perecerá", + "vcmi.battleWindow.killed" : "Eliminados", + "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s han sido eliminados por disparos certeros", + "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s ha sido eliminado por un disparo certero", + "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s han sido eliminados por disparos certeros", + "vcmi.battleWindow.endWithAutocombat" : "¿Quieres finalizar la batalla con combate automatizado?", "vcmi.battleResultsWindow.applyResultsLabel" : "Aplicar resultado de la batalla", @@ -225,6 +237,8 @@ "vcmi.heroWindow.openBackpack.hover" : "Abrir ventana de mochila de artefactos", "vcmi.heroWindow.openBackpack.help" : "Abre la ventana que facilita la gestión de la mochila de artefactos.", + "vcmi.tavernWindow.inviteHero" : "Invitar heroe", + "vcmi.commanderWindow.artifactMessage" : "¿Quieres devolver este artefacto al héroe?", "vcmi.creatureWindow.showBonuses.hover" : "Cambiar a vista de bonificaciones", @@ -321,6 +335,7 @@ "vcmi.map.victoryCondition.collectArtifacts.message" : "Adquirir tres artefactos", "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "¡Felicidades! Todos tus enemigos han sido derrotados y tienes la Alianza Angelical. ¡La victoria es tuya!", "vcmi.map.victoryCondition.angelicAlliance.message" : "Derrota a todos los enemigos y crea la Alianza Angelical", + "vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Por desgracia, has perdido parte de la Alianza Angélica. Todo se ha perdido.", // few strings from WoG used by vcmi "vcmi.stackExperience.description" : "» D e t a l l e s d e E x p e r i e n c i a d e l G r u p o «\n\nTipo de Criatura ................ : %s\nRango de Experiencia ............ : %s (%i)\nPuntos de Experiencia ............ : %i\nPuntos de Experiencia para el\nSiguiente Rango ............... : %i\nExperiencia Máxima por Batalla .. : %i%% (%i)\nNúmero de Criaturas en el grupo .. : %i\nMáximo de Nuevos Reclutas sin\nPerder el Rango Actual ......... : %i\nMultiplicador de Experiencia .... : %.2f\nMultiplicador de Actualización .. : %.2f\nExperiencia después del Rango 10 : %i\nMáximo de Nuevos Reclutas para\nMantener el Rango 10 si\nEstá en la Experiencia Máxima : %i", @@ -374,6 +389,8 @@ "core.bonus.ENCHANTER.description": "Puede lanzar ${subtype.spell} masivo cada turno", "core.bonus.ENCHANTED.name": "Encantado", "core.bonus.ENCHANTED.description": "Afectado por el hechizo permanente ${subtype.spell}", + "core.bonus.ENEMY_ATTACK_REDUCTION.name": "Ignorar ataque (${val}%)", + "core.bonus.ENEMY_ATTACK_REDUCTION.description": "Al ser atacado, ${val}% del daño del atacante es ignorado", "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorar Defensa (${val}%)", "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Ignora una parte de la defensa al atacar", "core.bonus.FIRE_IMMUNITY.name": "Inmunidad al Fuego", @@ -386,6 +403,8 @@ "core.bonus.FEAR.description": "Causa miedo a un grupo enemigo", "core.bonus.FEARLESS.name": "Inmune al miedo", "core.bonus.FEARLESS.description": "Inmune a la habilidad de miedo", + "core.bonus.FEROCITY.name": "Ferocidad", + "core.bonus.FEROCITY.description": "Ataca ${val} veces adicionales en caso de eliminar a alguien", "core.bonus.FLYING.name": "Volar", "core.bonus.FLYING.description": "Puede volar (ignora obstáculos)", "core.bonus.FREE_SHOOTING.name": "Disparo cercano", @@ -416,8 +435,8 @@ "core.bonus.MANA_DRAIN.description": "Drena ${val} de maná cada turno", "core.bonus.MAGIC_MIRROR.name": "Espejo mágico (${val}%)", "core.bonus.MAGIC_MIRROR.description": "Tiene una probabilidad del ${val}% de redirigir un hechizo ofensivo al enemigo", - "core.bonus.MAGIC_RESISTANCE.name": "Resistencia mágica (${MR}%)", - "core.bonus.MAGIC_RESISTANCE.description": "Tiene una probabilidad del ${MR}% de resistir el hechizo del enemigo", + "core.bonus.MAGIC_RESISTANCE.name": "Resistencia mágica (${val}%)", + "core.bonus.MAGIC_RESISTANCE.description": "Tiene una probabilidad del ${val}% de resistir el hechizo del enemigo", "core.bonus.MIND_IMMUNITY.name": "Inmunidad a hechizos mentales", "core.bonus.MIND_IMMUNITY.description": "Inmune a hechizos de tipo mental", "core.bonus.NO_DISTANCE_PENALTY.name": "Sin penalización por distancia", @@ -440,10 +459,12 @@ "core.bonus.REBIRTH.description": "El ${val}% del grupo resucitará después de la muerte", "core.bonus.RETURN_AFTER_STRIKE.name": "Atacar y volver", "core.bonus.RETURN_AFTER_STRIKE.description": "Regresa después de un ataque cuerpo a cuerpo", + "core.bonus.REVENGE.name": "Venganza", + "core.bonus.REVENGE.description": "Inflige daño adicional según la salud perdida del atacante en la batalla.", "core.bonus.SHOOTER.name": "A distancia", "core.bonus.SHOOTER.description": "La criatura puede disparar", "core.bonus.SHOOTS_ALL_ADJACENT.name": "Dispara en todas direcciones", - "core.bonus.SHOOTS_ALL_ADJACENT.description": "Los ataques a distancia de esta criatura impactan a todos los objetivos en un área pequeña", + "core.bonus.SHOOTS_ALL_ADJACENT.description": "Los ataques a distancia de esta criatura impactan a todos los objetivos en un área reducida", "core.bonus.SOUL_STEAL.name": "Roba almas", "core.bonus.SOUL_STEAL.description": "Gana ${val} nuevas criaturas por cada enemigo eliminado", "core.bonus.SPELLCASTER.name": "Lanzador de hechizos", From 4a0dd2da2c2e7d922c987e8844e14c334d9e4814 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 9 Feb 2024 23:02:41 +0200 Subject: [PATCH 93/98] Added option to create new account even if player already have one --- Mods/vcmi/config/vcmi/english.json | 2 + client/globalLobby/GlobalLobbyClient.cpp | 15 ++--- client/globalLobby/GlobalLobbyClient.h | 5 +- client/globalLobby/GlobalLobbyLoginWindow.cpp | 66 +++++++++++++++---- client/globalLobby/GlobalLobbyLoginWindow.h | 4 ++ 5 files changed, 67 insertions(+), 25 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index bca221500..699290e5c 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -76,6 +76,8 @@ "vcmi.lobby.login.username" : "Username:", "vcmi.lobby.login.connecting" : "Connecting...", "vcmi.lobby.login.error" : "Connection error: %s", + "vcmi.lobby.login.create" : "New Account", + "vcmi.lobby.login.login" : "Login", "vcmi.lobby.room.create" : "Create Room", "vcmi.lobby.room.players.limit" : "Players Limit", diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index 52acaee5d..3d2406e22 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -207,24 +207,21 @@ void GlobalLobbyClient::receiveJoinRoomSuccess(const JsonNode & json) void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr & connection) { boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); - networkConnection = connection; - JsonNode toSend; + auto loginWindowPtr = loginWindow.lock(); - std::string accountID = settings["lobby"]["accountID"].String(); + if(!loginWindowPtr || !GH.windows().topWindow()) + throw std::runtime_error("lobby connection established without active login window!"); - if(accountID.empty()) - sendClientRegister(); - else - sendClientLogin(); + loginWindowPtr->onConnectionSuccess(); } -void GlobalLobbyClient::sendClientRegister() +void GlobalLobbyClient::sendClientRegister(const std::string & accountName) { JsonNode toSend; toSend["type"].String() = "clientRegister"; - toSend["displayName"] = settings["lobby"]["displayName"]; + toSend["displayName"].String() = accountName; sendMessage(toSend); } diff --git a/client/globalLobby/GlobalLobbyClient.h b/client/globalLobby/GlobalLobbyClient.h index 5d8abb4e0..16d7beaaa 100644 --- a/client/globalLobby/GlobalLobbyClient.h +++ b/client/globalLobby/GlobalLobbyClient.h @@ -35,9 +35,6 @@ class GlobalLobbyClient final : public INetworkClientListener, boost::noncopyabl void onConnectionEstablished(const std::shared_ptr &) override; void onDisconnected(const std::shared_ptr &, const std::string & errorMessage) override; - void sendClientRegister(); - void sendClientLogin(); - void receiveAccountCreated(const JsonNode & json); void receiveOperationFailed(const JsonNode & json); void receiveLoginSuccess(const JsonNode & json); @@ -60,6 +57,8 @@ public: /// Activate interface and pushes lobby UI as top window void activateInterface(); void sendMessage(const JsonNode & data); + void sendClientRegister(const std::string & accountName); + void sendClientLogin(); void sendOpenPublicRoom(); void sendOpenPrivateRoom(); diff --git a/client/globalLobby/GlobalLobbyLoginWindow.cpp b/client/globalLobby/GlobalLobbyLoginWindow.cpp index 7403b206a..673941c9f 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.cpp +++ b/client/globalLobby/GlobalLobbyLoginWindow.cpp @@ -32,28 +32,58 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - pos.w = 200; - pos.h = 200; + pos.w = 284; + pos.h = 220; filledBackground = std::make_shared(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h)); labelTitle = std::make_shared( pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.lobby.login.title")); - labelUsername = std::make_shared( 10, 45, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.lobby.login.username")); - backgroundUsername = std::make_shared(Rect(10, 70, 180, 20), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64)); - inputUsername = std::make_shared(Rect(15, 73, 176, 16), FONT_SMALL, nullptr, ETextAlignment::TOPLEFT, true); - buttonLogin = std::make_shared(Point(10, 160), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onLogin(); }); - buttonClose = std::make_shared(Point(126, 160), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }); - labelStatus = std::make_shared( "", Rect(15, 95, 175, 60), 1, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); + labelUsername = std::make_shared( 10, 65, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.lobby.login.username")); + backgroundUsername = std::make_shared(Rect(10, 90, 264, 20), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64)); + inputUsername = std::make_shared(Rect(15, 93, 260, 16), FONT_SMALL, nullptr, ETextAlignment::TOPLEFT, true); + buttonLogin = std::make_shared(Point(10, 180), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onLogin(); }); + buttonClose = std::make_shared(Point(210, 180), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }); + labelStatus = std::make_shared( "", Rect(15, 115, 255, 60), 1, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); + + auto buttonRegister = std::make_shared(Point(10, 40), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0); + auto buttonLogin = std::make_shared(Point(146, 40), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0); + buttonRegister->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.login.create"), EFonts::FONT_SMALL, Colors::YELLOW); + buttonLogin->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.login.login"), EFonts::FONT_SMALL, Colors::YELLOW); + + toggleMode = std::make_shared(nullptr); + toggleMode->addToggle(0, buttonRegister); + toggleMode->addToggle(1, buttonLogin); + toggleMode->setSelected(settings["lobby"]["roomType"].Integer()); + toggleMode->addCallback([this](int index){onLoginModeChanged(index);}); + + if (settings["lobby"]["accountID"].String().empty()) + { + buttonLogin->block(true); + toggleMode->setSelected(0); + } + else + toggleMode->setSelected(1); filledBackground->playerColored(PlayerColor(1)); - inputUsername->setText(settings["lobby"]["displayName"].String()); inputUsername->cb += [this](const std::string & text) { - buttonLogin->block(text.empty()); + this->buttonLogin->block(text.empty()); }; center(); } +void GlobalLobbyLoginWindow::onLoginModeChanged(int value) +{ + if (value == 0) + { + inputUsername->setText(""); + } + else + { + inputUsername->setText(settings["lobby"]["displayName"].String()); + } +} + void GlobalLobbyLoginWindow::onClose() { close(); @@ -62,16 +92,26 @@ void GlobalLobbyLoginWindow::onClose() void GlobalLobbyLoginWindow::onLogin() { - Settings config = settings.write["lobby"]["displayName"]; - config->String() = inputUsername->getText(); - labelStatus->setText(CGI->generaltexth->translate("vcmi.lobby.login.connecting")); if(!CSH->getGlobalLobby().isConnected()) CSH->getGlobalLobby().connect(); + else + onConnectionSuccess(); + buttonClose->block(true); } void GlobalLobbyLoginWindow::onConnectionSuccess() +{ + std::string accountID = settings["lobby"]["accountID"].String(); + + if(toggleMode->getSelected() == 0) + CSH->getGlobalLobby().sendClientRegister(inputUsername->getText()); + else + CSH->getGlobalLobby().sendClientLogin(); +} + +void GlobalLobbyLoginWindow::onLoginSuccess() { close(); CSH->getGlobalLobby().activateInterface(); diff --git a/client/globalLobby/GlobalLobbyLoginWindow.h b/client/globalLobby/GlobalLobbyLoginWindow.h index 54bccc182..a589ac650 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.h +++ b/client/globalLobby/GlobalLobbyLoginWindow.h @@ -14,6 +14,7 @@ class CLabel; class CTextBox; class CTextInput; +class CToggleGroup; class FilledTexturePlayerColored; class TransparentFilledRectangle; class CButton; @@ -29,7 +30,9 @@ class GlobalLobbyLoginWindow : public CWindowObject std::shared_ptr buttonLogin; std::shared_ptr buttonClose; + std::shared_ptr toggleMode; // create account or use existing + void onLoginModeChanged(int value); void onClose(); void onLogin(); @@ -37,5 +40,6 @@ public: GlobalLobbyLoginWindow(); void onConnectionSuccess(); + void onLoginSuccess(); void onConnectionFailed(const std::string & reason); }; From 1a144fc516fec7995d2b6552140b59ecad571668 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 10 Feb 2024 19:02:25 +0200 Subject: [PATCH 94/98] Some progress on private rooms support --- client/CServerHandler.cpp | 10 ++++++++++ client/CServerHandler.h | 2 ++ client/globalLobby/GlobalLobbyClient.cpp | 10 +++++++++- client/globalLobby/GlobalLobbyClient.h | 3 ++- client/globalLobby/GlobalLobbyWidget.cpp | 8 ++++++-- client/globalLobby/GlobalLobbyWidget.h | 4 ---- client/globalLobby/GlobalLobbyWindow.cpp | 16 +++++++++++++++- client/globalLobby/GlobalLobbyWindow.h | 2 ++ lobby/LobbyServer.cpp | 8 +++++--- server/GlobalLobbyProcessor.cpp | 24 +++++++++++++++++------- server/GlobalLobbyProcessor.h | 2 -- 11 files changed, 68 insertions(+), 21 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index a8b7deaf0..f7f8e585b 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -997,3 +997,13 @@ void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const if(getState() != EClientState::STARTING) c->sendPack(&pack); } + +bool CServerHandler::inLobbyRoom() const +{ + return CSH->serverMode == EServerMode::LOBBY_HOST || CSH->serverMode == EServerMode::LOBBY_GUEST; +} + +bool CServerHandler::inGame() const +{ + return c != nullptr; +} diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 62ce39f64..4ec530209 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -165,6 +165,8 @@ public: bool isHost() const; bool isGuest() const; + bool inLobbyRoom() const; + bool inGame() const; const std::string & getCurrentHostname() const; const std::string & getLocalHostname() const; diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index 3d2406e22..f529602ee 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -66,6 +66,9 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptrerror("Received unexpected message from lobby server: %s", json["type"].String()); } @@ -188,6 +191,11 @@ void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json) lobbyWindowPtr->onActiveRooms(activeRooms); } +void GlobalLobbyClient::receiveInviteReceived(const JsonNode & json) +{ + assert(0); //TODO +} + void GlobalLobbyClient::receiveJoinRoomSuccess(const JsonNode & json) { Settings configRoom = settings.write["lobby"]["roomID"]; @@ -293,7 +301,7 @@ void GlobalLobbyClient::connect() CSH->getNetworkHandler().connectToRemote(*this, hostname, port); } -bool GlobalLobbyClient::isConnected() +bool GlobalLobbyClient::isConnected() const { return networkConnection != nullptr; } diff --git a/client/globalLobby/GlobalLobbyClient.h b/client/globalLobby/GlobalLobbyClient.h index 16d7beaaa..b269c7018 100644 --- a/client/globalLobby/GlobalLobbyClient.h +++ b/client/globalLobby/GlobalLobbyClient.h @@ -43,6 +43,7 @@ class GlobalLobbyClient final : public INetworkClientListener, boost::noncopyabl void receiveActiveAccounts(const JsonNode & json); void receiveActiveGameRooms(const JsonNode & json); void receiveJoinRoomSuccess(const JsonNode & json); + void receiveInviteReceived(const JsonNode & json); std::shared_ptr createLoginWindow(); std::shared_ptr createLobbyWindow(); @@ -65,5 +66,5 @@ public: void sendProxyConnectionLogin(const NetworkConnectionPtr & netConnection); void connect(); - bool isConnected(); + bool isConnected() const; }; diff --git a/client/globalLobby/GlobalLobbyWidget.cpp b/client/globalLobby/GlobalLobbyWidget.cpp index c7bb40e6a..2be6b21e8 100644 --- a/client/globalLobby/GlobalLobbyWidget.cpp +++ b/client/globalLobby/GlobalLobbyWidget.cpp @@ -123,7 +123,9 @@ GlobalLobbyAccountCard::GlobalLobbyAccountCard(GlobalLobbyWindow * window, const backgroundOverlay = std::make_shared(Rect(0, 0, pos.w, pos.h), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64)); labelName = std::make_shared( 5, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, accountDescription.displayName); labelStatus = std::make_shared( 5, 20, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, accountDescription.status); - buttonInvite = std::make_shared(Point(95, 8), AnimationPath::builtin("settingsWindow/button32"), CButton::tooltip(), onInviteClicked); + + if (CSH->inLobbyRoom()) + buttonInvite = std::make_shared(Point(95, 8), AnimationPath::builtin("settingsWindow/button32"), CButton::tooltip(), onInviteClicked); } GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription) @@ -146,5 +148,7 @@ GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const Globa labelName = std::make_shared( 5, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName); labelStatus = std::make_shared( 5, 20, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, roomDescription.description); labelRoomSize = std::make_shared( 160, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, roomSizeText.toString()); - buttonJoin = std::make_shared(Point(195, 8), AnimationPath::builtin("settingsWindow/button32"), CButton::tooltip(), onJoinClicked); + + if (!CSH->inGame()) + buttonJoin = std::make_shared(Point(195, 8), AnimationPath::builtin("settingsWindow/button32"), CButton::tooltip(), onJoinClicked); } diff --git a/client/globalLobby/GlobalLobbyWidget.h b/client/globalLobby/GlobalLobbyWidget.h index 61caaaa21..c4a52e688 100644 --- a/client/globalLobby/GlobalLobbyWidget.h +++ b/client/globalLobby/GlobalLobbyWidget.h @@ -38,8 +38,6 @@ class GlobalLobbyAccountCard : public CIntObject public: GlobalLobbyAccountCard(GlobalLobbyWindow * window, const GlobalLobbyAccount & accountDescription); - GlobalLobbyWindow * window; - std::shared_ptr backgroundOverlay; std::shared_ptr labelName; std::shared_ptr labelStatus; @@ -51,8 +49,6 @@ class GlobalLobbyRoomCard : public CIntObject public: GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription); - GlobalLobbyWindow * window; - std::shared_ptr backgroundOverlay; std::shared_ptr labelName; std::shared_ptr labelRoomSize; diff --git a/client/globalLobby/GlobalLobbyWindow.cpp b/client/globalLobby/GlobalLobbyWindow.cpp index 8aa54378e..5ef963cfc 100644 --- a/client/globalLobby/GlobalLobbyWindow.cpp +++ b/client/globalLobby/GlobalLobbyWindow.cpp @@ -55,7 +55,11 @@ void GlobalLobbyWindow::doCreateGameRoom() void GlobalLobbyWindow::doInviteAccount(const std::string & accountID) { - assert(0); // TODO + JsonNode toSend; + toSend["type"].String() = "sendInvite"; + toSend["accountID"].String() = accountID; + + CSH->getGlobalLobby().sendMessage(toSend); } void GlobalLobbyWindow::doJoinRoom(const std::string & roomID) @@ -89,3 +93,13 @@ void GlobalLobbyWindow::onActiveRooms(const std::vector & rooms { widget->getRoomList()->reset(); } + +void GlobalLobbyWindow::onJoinedRoom() +{ + widget->getAccountList()->reset(); +} + +void GlobalLobbyWindow::onLeftRoom() +{ + widget->getAccountList()->reset(); +} diff --git a/client/globalLobby/GlobalLobbyWindow.h b/client/globalLobby/GlobalLobbyWindow.h index e3c60a9d4..d6d868653 100644 --- a/client/globalLobby/GlobalLobbyWindow.h +++ b/client/globalLobby/GlobalLobbyWindow.h @@ -33,4 +33,6 @@ public: void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when); void onActiveAccounts(const std::vector & accounts); void onActiveRooms(const std::vector & rooms); + void onJoinedRoom(); + void onLeftRoom(); }; diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 15078de65..70fd2470a 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -526,12 +526,14 @@ void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, c void LobbyServer::receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json) { - std::string gameRoomID = json["gameRoomID"].String(); - std::string senderName = activeAccounts[connection]; + std::string accountID = json["accountID"].String(); + std::string gameRoomID = activeGameRooms[connection]; - if(!database->isPlayerInGameRoom(senderName, gameRoomID)) + if(!database->isPlayerInGameRoom(accountID, gameRoomID)) return sendOperationFailed(connection, "You are not in the room!"); + database->deletePlayerFromGameRoom(accountID, gameRoomID); + broadcastActiveGameRooms(); } diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index 5b0bae7d6..df42d9fb4 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -36,6 +36,21 @@ void GlobalLobbyProcessor::onDisconnected(const std::shared_ptrsendPacket(message.toBytes(true)); + break; + } + } + } + // player disconnected owner.onDisconnected(connection, errorMessage); } @@ -107,7 +122,7 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptrsendPacket(toSend.toBytes(true)); } else { @@ -122,14 +137,9 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptrsendPacket(toSend.toBytes(true)); proxyConnections[guestAccountID] = connection; owner.onNewConnection(connection); } } - -void GlobalLobbyProcessor::sendMessage(const NetworkConnectionPtr & target, const JsonNode & data) -{ - target->sendPacket(data.toBytes(true)); -} diff --git a/server/GlobalLobbyProcessor.h b/server/GlobalLobbyProcessor.h index 74267d160..4c81cc70c 100644 --- a/server/GlobalLobbyProcessor.h +++ b/server/GlobalLobbyProcessor.h @@ -29,8 +29,6 @@ class GlobalLobbyProcessor : public INetworkClientListener void onConnectionFailed(const std::string & errorMessage) override; void onConnectionEstablished(const std::shared_ptr &) override; - void sendMessage(const NetworkConnectionPtr & target, const JsonNode & data); - void receiveOperationFailed(const JsonNode & json); void receiveLoginSuccess(const JsonNode & json); void receiveAccountJoinsRoom(const JsonNode & json); From 522cb571b3cf0c6d7fdcc627e5d618a2e4c80375 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Sat, 10 Feb 2024 20:22:08 +0100 Subject: [PATCH 95/98] Remove redundant `virtual` specifiers `grep -nr virtual | grep -v googletest | grep override > ../redundant_virtual.txt` ```python import os with open("../redundant_virtual.txt") as f: for line in f: print() line: str = line.strip() print(line) tmp = line.split(":") file = tmp[0].strip() code = tmp[-1].strip() print(file) print(code) new_code = code.replace("virtual ", "", 1) # https://superuser.com/a/802490/578501 command = f"export FIND='{code}' && export REPLACE='{new_code}' && ruby -p -i -e \"gsub(ENV['FIND'], ENV['REPLACE'])\" {file}" os.system(command) ``` --- AI/EmptyAI/CEmptyAI.h | 4 +- AI/Nullkiller/Behaviors/BuildingBehavior.h | 2 +- AI/Nullkiller/Behaviors/BuyArmyBehavior.h | 2 +- .../Behaviors/CaptureObjectsBehavior.h | 2 +- AI/Nullkiller/Behaviors/ClusterBehavior.h | 4 +- AI/Nullkiller/Behaviors/DefenceBehavior.h | 2 +- AI/Nullkiller/Behaviors/GatherArmyBehavior.h | 4 +- AI/Nullkiller/Behaviors/RecruitHeroBehavior.h | 4 +- AI/Nullkiller/Behaviors/StartupBehavior.h | 4 +- AI/Nullkiller/Behaviors/StayAtTownBehavior.h | 4 +- AI/Nullkiller/Goals/AdventureSpellCast.h | 2 +- AI/Nullkiller/Goals/Build.h | 2 +- AI/Nullkiller/Goals/BuildBoat.h | 2 +- AI/Nullkiller/Goals/BuildThis.h | 2 +- AI/Nullkiller/Goals/BuyArmy.h | 4 +- AI/Nullkiller/Goals/CGoal.h | 8 +-- AI/Nullkiller/Goals/CaptureObject.h | 6 +- AI/Nullkiller/Goals/CompleteQuest.h | 6 +- AI/Nullkiller/Goals/Composition.h | 8 +-- AI/Nullkiller/Goals/DigAtTile.h | 2 +- AI/Nullkiller/Goals/DismissHero.h | 2 +- AI/Nullkiller/Goals/ExchangeSwapTownHeroes.h | 2 +- AI/Nullkiller/Goals/ExecuteHeroChain.h | 4 +- AI/Nullkiller/Goals/GatherArmy.h | 2 +- AI/Nullkiller/Goals/Invalid.h | 4 +- AI/Nullkiller/Goals/RecruitHero.h | 2 +- AI/Nullkiller/Goals/SaveResources.h | 2 +- AI/Nullkiller/Goals/StayAtTown.h | 2 +- AI/Nullkiller/Goals/Trade.h | 2 +- AI/Nullkiller/Markers/ArmyUpgrade.h | 2 +- AI/Nullkiller/Markers/DefendTown.h | 2 +- AI/Nullkiller/Markers/HeroExchange.h | 2 +- AI/Nullkiller/Markers/UnlockCluster.h | 2 +- AI/Nullkiller/Pathfinding/AINodeStorage.h | 2 +- .../Pathfinding/AIPathfinderConfig.h | 2 +- .../AdventureSpellCastMovementActions.h | 4 +- .../Pathfinding/Actions/BattleAction.h | 2 +- .../Pathfinding/Actions/BoatActions.h | 14 ++-- .../Pathfinding/Actions/QuestAction.h | 4 +- .../Pathfinding/Actions/TownPortalAction.h | 2 +- AI/Nullkiller/Pathfinding/Actors.h | 4 +- AI/VCAI/Goals/AdventureSpellCast.h | 2 +- AI/VCAI/Goals/Build.h | 2 +- AI/VCAI/Goals/BuildBoat.h | 2 +- AI/VCAI/Goals/BuildThis.h | 2 +- AI/VCAI/Goals/BuyArmy.h | 2 +- AI/VCAI/Goals/CGoal.h | 2 +- AI/VCAI/Goals/ClearWayTo.h | 2 +- AI/VCAI/Goals/CollectRes.h | 2 +- AI/VCAI/Goals/CompleteQuest.h | 2 +- AI/VCAI/Goals/Conquer.h | 2 +- AI/VCAI/Goals/DigAtTile.h | 2 +- AI/VCAI/Goals/Explore.h | 2 +- AI/VCAI/Goals/FindObj.h | 2 +- AI/VCAI/Goals/GatherArmy.h | 2 +- AI/VCAI/Goals/GatherTroops.h | 2 +- AI/VCAI/Goals/GetArtOfType.h | 2 +- AI/VCAI/Goals/Invalid.h | 2 +- AI/VCAI/Goals/RecruitHero.h | 2 +- AI/VCAI/Goals/Trade.h | 2 +- AI/VCAI/Goals/VisitHero.h | 2 +- AI/VCAI/Goals/VisitObj.h | 2 +- AI/VCAI/Goals/VisitTile.h | 2 +- AI/VCAI/Goals/Win.h | 2 +- AI/VCAI/Pathfinding/AINodeStorage.h | 2 +- AI/VCAI/Pathfinding/AIPathfinderConfig.h | 2 +- client/CServerHandler.cpp | 6 +- client/Client.h | 2 +- client/ClientNetPackVisitors.h | 24 +++---- client/LobbyClientNetPackVisitors.h | 24 +++---- client/widgets/MiscWidgets.h | 2 +- client/windows/InfoWindows.h | 2 +- lib/CArtHandler.h | 2 +- lib/CCreatureHandler.h | 2 +- lib/CGameInfoCallback.h | 4 +- lib/CGameInterface.h | 28 ++++---- lib/bonuses/Updaters.h | 10 +-- lib/mapObjects/MiscObjects.h | 2 +- lib/networkPacks/PacksForLobby.h | 2 +- lib/pathfinder/CGPathNode.h | 2 +- lib/pathfinder/NodeStorage.h | 2 +- lib/pathfinder/PathfinderOptions.h | 2 +- lib/rmg/modificators/Modificator.h | 2 +- scripting/lua/LuaSpellEffect.h | 2 +- scripting/lua/api/BonusSystem.h | 4 +- server/CVCMIServer.cpp | 8 +-- server/LobbyNetPackVisitors.h | 64 +++++++++---------- server/ServerNetPackVisitors.h | 60 ++++++++--------- server/queries/BattleQueries.h | 10 +-- server/queries/CQuery.h | 4 +- server/queries/MapQueries.h | 28 ++++---- test/CVcmiTestConfig.h | 4 +- 92 files changed, 245 insertions(+), 245 deletions(-) diff --git a/AI/EmptyAI/CEmptyAI.h b/AI/EmptyAI/CEmptyAI.h index 2954564be..eb2935f83 100644 --- a/AI/EmptyAI/CEmptyAI.h +++ b/AI/EmptyAI/CEmptyAI.h @@ -19,8 +19,8 @@ class CEmptyAI : public CGlobalAI std::shared_ptr cb; public: - virtual void saveGame(BinarySerializer & h) override; - virtual void loadGame(BinaryDeserializer & h) override; + void saveGame(BinarySerializer & h) override; + void loadGame(BinaryDeserializer & h) override; void initGameInterface(std::shared_ptr ENV, std::shared_ptr CB) override; void yourTurn(QueryID queryID) override; diff --git a/AI/Nullkiller/Behaviors/BuildingBehavior.h b/AI/Nullkiller/Behaviors/BuildingBehavior.h index 813a37619..46d12dd13 100644 --- a/AI/Nullkiller/Behaviors/BuildingBehavior.h +++ b/AI/Nullkiller/Behaviors/BuildingBehavior.h @@ -27,7 +27,7 @@ namespace Goals virtual Goals::TGoalVec decompose() const override; virtual std::string toString() const override; - virtual bool operator==(const BuildingBehavior & other) const override + bool operator==(const BuildingBehavior & other) const override { return true; } diff --git a/AI/Nullkiller/Behaviors/BuyArmyBehavior.h b/AI/Nullkiller/Behaviors/BuyArmyBehavior.h index a6bf681c7..2d6ea9f0d 100644 --- a/AI/Nullkiller/Behaviors/BuyArmyBehavior.h +++ b/AI/Nullkiller/Behaviors/BuyArmyBehavior.h @@ -26,7 +26,7 @@ namespace Goals virtual Goals::TGoalVec decompose() const override; virtual std::string toString() const override; - virtual bool operator==(const BuyArmyBehavior & other) const override + bool operator==(const BuyArmyBehavior & other) const override { return true; } diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h index ce075c858..1e29fb03c 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h @@ -65,7 +65,7 @@ namespace Goals return *this; } - virtual bool operator==(const CaptureObjectsBehavior & other) const override; + bool operator==(const CaptureObjectsBehavior & other) const override; static Goals::TGoalVec getVisitGoals(const std::vector & paths, const CGObjectInstance * objToVisit = nullptr); diff --git a/AI/Nullkiller/Behaviors/ClusterBehavior.h b/AI/Nullkiller/Behaviors/ClusterBehavior.h index e5a52f73c..22c88f2ce 100644 --- a/AI/Nullkiller/Behaviors/ClusterBehavior.h +++ b/AI/Nullkiller/Behaviors/ClusterBehavior.h @@ -28,10 +28,10 @@ namespace Goals { } - virtual TGoalVec decompose() const override; + TGoalVec decompose() const override; virtual std::string toString() const override; - virtual bool operator==(const ClusterBehavior & other) const override + bool operator==(const ClusterBehavior & other) const override { return true; } diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.h b/AI/Nullkiller/Behaviors/DefenceBehavior.h index fab4745f5..1eb4ef682 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.h +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.h @@ -32,7 +32,7 @@ namespace Goals virtual Goals::TGoalVec decompose() const override; virtual std::string toString() const override; - virtual bool operator==(const DefenceBehavior & other) const override + bool operator==(const DefenceBehavior & other) const override { return true; } diff --git a/AI/Nullkiller/Behaviors/GatherArmyBehavior.h b/AI/Nullkiller/Behaviors/GatherArmyBehavior.h index b933575ba..1ae2f3481 100644 --- a/AI/Nullkiller/Behaviors/GatherArmyBehavior.h +++ b/AI/Nullkiller/Behaviors/GatherArmyBehavior.h @@ -25,10 +25,10 @@ namespace Goals { } - virtual TGoalVec decompose() const override; + TGoalVec decompose() const override; virtual std::string toString() const override; - virtual bool operator==(const GatherArmyBehavior & other) const override + bool operator==(const GatherArmyBehavior & other) const override { return true; } diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.h b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.h index 4d1102b4d..103f25d50 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.h +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.h @@ -25,10 +25,10 @@ namespace Goals { } - virtual TGoalVec decompose() const override; + TGoalVec decompose() const override; virtual std::string toString() const override; - virtual bool operator==(const RecruitHeroBehavior & other) const override + bool operator==(const RecruitHeroBehavior & other) const override { return true; } diff --git a/AI/Nullkiller/Behaviors/StartupBehavior.h b/AI/Nullkiller/Behaviors/StartupBehavior.h index 12cfb33c2..8bfcc5734 100644 --- a/AI/Nullkiller/Behaviors/StartupBehavior.h +++ b/AI/Nullkiller/Behaviors/StartupBehavior.h @@ -25,10 +25,10 @@ namespace Goals { } - virtual TGoalVec decompose() const override; + TGoalVec decompose() const override; virtual std::string toString() const override; - virtual bool operator==(const StartupBehavior & other) const override + bool operator==(const StartupBehavior & other) const override { return true; } diff --git a/AI/Nullkiller/Behaviors/StayAtTownBehavior.h b/AI/Nullkiller/Behaviors/StayAtTownBehavior.h index 260cf136a..dd8363968 100644 --- a/AI/Nullkiller/Behaviors/StayAtTownBehavior.h +++ b/AI/Nullkiller/Behaviors/StayAtTownBehavior.h @@ -25,10 +25,10 @@ namespace Goals { } - virtual TGoalVec decompose() const override; + TGoalVec decompose() const override; virtual std::string toString() const override; - virtual bool operator==(const StayAtTownBehavior & other) const override + bool operator==(const StayAtTownBehavior & other) const override { return true; } diff --git a/AI/Nullkiller/Goals/AdventureSpellCast.h b/AI/Nullkiller/Goals/AdventureSpellCast.h index 23cfc4a97..de403905a 100644 --- a/AI/Nullkiller/Goals/AdventureSpellCast.h +++ b/AI/Nullkiller/Goals/AdventureSpellCast.h @@ -35,7 +35,7 @@ namespace Goals void accept(AIGateway * ai) override; std::string toString() const override; - virtual bool operator==(const AdventureSpellCast & other) const override; + bool operator==(const AdventureSpellCast & other) const override; }; } diff --git a/AI/Nullkiller/Goals/Build.h b/AI/Nullkiller/Goals/Build.h index 8ff8d3228..cb69dc9b1 100644 --- a/AI/Nullkiller/Goals/Build.h +++ b/AI/Nullkiller/Goals/Build.h @@ -32,7 +32,7 @@ namespace Goals TSubgoal whatToDoToAchieve() override; bool fulfillsMe(TSubgoal goal) override; - virtual bool operator==(const Build & other) const override + bool operator==(const Build & other) const override { return true; } diff --git a/AI/Nullkiller/Goals/BuildBoat.h b/AI/Nullkiller/Goals/BuildBoat.h index 31383d256..1a2432081 100644 --- a/AI/Nullkiller/Goals/BuildBoat.h +++ b/AI/Nullkiller/Goals/BuildBoat.h @@ -29,7 +29,7 @@ namespace Goals void accept(AIGateway * ai) override; std::string toString() const override; - virtual bool operator==(const BuildBoat & other) const override; + bool operator==(const BuildBoat & other) const override; }; } diff --git a/AI/Nullkiller/Goals/BuildThis.h b/AI/Nullkiller/Goals/BuildThis.h index f4bacbc5e..0b85108b6 100644 --- a/AI/Nullkiller/Goals/BuildThis.h +++ b/AI/Nullkiller/Goals/BuildThis.h @@ -39,7 +39,7 @@ namespace Goals } BuildThis(BuildingID Bid, const CGTownInstance * tid); - virtual bool operator==(const BuildThis & other) const override; + bool operator==(const BuildThis & other) const override; virtual std::string toString() const override; void accept(AIGateway * ai) override; }; diff --git a/AI/Nullkiller/Goals/BuyArmy.h b/AI/Nullkiller/Goals/BuyArmy.h index d4fdc6166..bd1080fd9 100644 --- a/AI/Nullkiller/Goals/BuyArmy.h +++ b/AI/Nullkiller/Goals/BuyArmy.h @@ -36,11 +36,11 @@ namespace Goals priority = 3;//TODO: evaluate? } - virtual bool operator==(const BuyArmy & other) const override; + bool operator==(const BuyArmy & other) const override; virtual std::string toString() const override; - virtual void accept(AIGateway * ai) override; + void accept(AIGateway * ai) override; }; } diff --git a/AI/Nullkiller/Goals/CGoal.h b/AI/Nullkiller/Goals/CGoal.h index 255535d59..8aa5b90ad 100644 --- a/AI/Nullkiller/Goals/CGoal.h +++ b/AI/Nullkiller/Goals/CGoal.h @@ -44,7 +44,7 @@ namespace Goals //h & value & resID & objid & aid & tile & hero & town & bid; } - virtual bool operator==(const AbstractGoal & g) const override + bool operator==(const AbstractGoal & g) const override { if(goalType != g.goalType) return false; @@ -54,7 +54,7 @@ namespace Goals virtual bool operator==(const T & other) const = 0; - virtual TGoalVec decompose() const override + TGoalVec decompose() const override { TSubgoal single = decomposeSingle(); @@ -90,11 +90,11 @@ namespace Goals return *((T *)this); } - virtual bool isElementar() const override { return true; } + bool isElementar() const override { return true; } virtual HeroPtr getHero() const override { return AbstractGoal::hero; } - virtual int getHeroExchangeCount() const override { return 0; } + int getHeroExchangeCount() const override { return 0; } }; } diff --git a/AI/Nullkiller/Goals/CaptureObject.h b/AI/Nullkiller/Goals/CaptureObject.h index e78993638..5ef3a8d51 100644 --- a/AI/Nullkiller/Goals/CaptureObject.h +++ b/AI/Nullkiller/Goals/CaptureObject.h @@ -34,11 +34,11 @@ namespace Goals name = obj->getObjectName(); } - virtual bool operator==(const CaptureObject & other) const override; + bool operator==(const CaptureObject & other) const override; virtual Goals::TGoalVec decompose() const override; virtual std::string toString() const override; - virtual bool hasHash() const override { return true; } - virtual uint64_t getHash() const override; + bool hasHash() const override { return true; } + uint64_t getHash() const override; }; } diff --git a/AI/Nullkiller/Goals/CompleteQuest.h b/AI/Nullkiller/Goals/CompleteQuest.h index 59ed0dc31..e7e0f3386 100644 --- a/AI/Nullkiller/Goals/CompleteQuest.h +++ b/AI/Nullkiller/Goals/CompleteQuest.h @@ -31,10 +31,10 @@ namespace Goals virtual Goals::TGoalVec decompose() const override; virtual std::string toString() const override; - virtual bool hasHash() const override { return true; } - virtual uint64_t getHash() const override; + bool hasHash() const override { return true; } + uint64_t getHash() const override; - virtual bool operator==(const CompleteQuest & other) const override; + bool operator==(const CompleteQuest & other) const override; private: TGoalVec tryCompleteQuest() const; diff --git a/AI/Nullkiller/Goals/Composition.h b/AI/Nullkiller/Goals/Composition.h index ecb4a1ab9..72bf4e9bb 100644 --- a/AI/Nullkiller/Goals/Composition.h +++ b/AI/Nullkiller/Goals/Composition.h @@ -26,15 +26,15 @@ namespace Goals { } - virtual bool operator==(const Composition & other) const override; + bool operator==(const Composition & other) const override; virtual std::string toString() const override; void accept(AIGateway * ai) override; Composition & addNext(const AbstractGoal & goal); Composition & addNext(TSubgoal goal); Composition & addNextSequence(const TGoalVec & taskSequence); - virtual TGoalVec decompose() const override; - virtual bool isElementar() const override; - virtual int getHeroExchangeCount() const override; + TGoalVec decompose() const override; + bool isElementar() const override; + int getHeroExchangeCount() const override; }; } diff --git a/AI/Nullkiller/Goals/DigAtTile.h b/AI/Nullkiller/Goals/DigAtTile.h index b50708c9b..c99d61d0b 100644 --- a/AI/Nullkiller/Goals/DigAtTile.h +++ b/AI/Nullkiller/Goals/DigAtTile.h @@ -33,7 +33,7 @@ namespace Goals { tile = Tile; } - virtual bool operator==(const DigAtTile & other) const override; + bool operator==(const DigAtTile & other) const override; private: //TSubgoal decomposeSingle() const override; diff --git a/AI/Nullkiller/Goals/DismissHero.h b/AI/Nullkiller/Goals/DismissHero.h index a6d180300..98bfdf1fa 100644 --- a/AI/Nullkiller/Goals/DismissHero.h +++ b/AI/Nullkiller/Goals/DismissHero.h @@ -26,7 +26,7 @@ namespace Goals void accept(AIGateway * ai) override; std::string toString() const override; - virtual bool operator==(const DismissHero & other) const override; + bool operator==(const DismissHero & other) const override; }; } diff --git a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.h b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.h index ca25f280b..8a86c364e 100644 --- a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.h +++ b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.h @@ -31,7 +31,7 @@ namespace Goals void accept(AIGateway * ai) override; std::string toString() const override; - virtual bool operator==(const ExchangeSwapTownHeroes & other) const override; + bool operator==(const ExchangeSwapTownHeroes & other) const override; const CGHeroInstance * getGarrisonHero() const { return garrisonHero; } HeroLockedReason getLockingReason() const { return lockingReason; } diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.h b/AI/Nullkiller/Goals/ExecuteHeroChain.h index b22e18499..a0e0ff39e 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.h +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.h @@ -30,10 +30,10 @@ namespace Goals void accept(AIGateway * ai) override; std::string toString() const override; - virtual bool operator==(const ExecuteHeroChain & other) const override; + bool operator==(const ExecuteHeroChain & other) const override; const AIPath & getPath() const { return chainPath; } - virtual int getHeroExchangeCount() const override { return chainPath.exchangeCount; } + int getHeroExchangeCount() const override { return chainPath.exchangeCount; } private: bool moveHeroToTile(const CGHeroInstance * hero, const int3 & tile); diff --git a/AI/Nullkiller/Goals/GatherArmy.h b/AI/Nullkiller/Goals/GatherArmy.h index 9dbb81ec3..804a159c3 100644 --- a/AI/Nullkiller/Goals/GatherArmy.h +++ b/AI/Nullkiller/Goals/GatherArmy.h @@ -36,7 +36,7 @@ namespace Goals TGoalVec getAllPossibleSubgoals() override; TSubgoal whatToDoToAchieve() override; std::string completeMessage() const override; - virtual bool operator==(const GatherArmy & other) const override; + bool operator==(const GatherArmy & other) const override; }; } diff --git a/AI/Nullkiller/Goals/Invalid.h b/AI/Nullkiller/Goals/Invalid.h index 6c2d7afa4..9c02e3091 100644 --- a/AI/Nullkiller/Goals/Invalid.h +++ b/AI/Nullkiller/Goals/Invalid.h @@ -32,7 +32,7 @@ namespace Goals return TGoalVec(); } - virtual bool operator==(const Invalid & other) const override + bool operator==(const Invalid & other) const override { return true; } @@ -42,7 +42,7 @@ namespace Goals return "Invalid"; } - virtual void accept(AIGateway * ai) override + void accept(AIGateway * ai) override { throw cannotFulfillGoalException("Can not fulfill Invalid goal!"); } diff --git a/AI/Nullkiller/Goals/RecruitHero.h b/AI/Nullkiller/Goals/RecruitHero.h index 78c6b0867..243f1f6d2 100644 --- a/AI/Nullkiller/Goals/RecruitHero.h +++ b/AI/Nullkiller/Goals/RecruitHero.h @@ -38,7 +38,7 @@ namespace Goals { } - virtual bool operator==(const RecruitHero & other) const override + bool operator==(const RecruitHero & other) const override { return true; } diff --git a/AI/Nullkiller/Goals/SaveResources.h b/AI/Nullkiller/Goals/SaveResources.h index eb0fe3b5b..09438b488 100644 --- a/AI/Nullkiller/Goals/SaveResources.h +++ b/AI/Nullkiller/Goals/SaveResources.h @@ -28,7 +28,7 @@ namespace Goals void accept(AIGateway * ai) override; std::string toString() const override; - virtual bool operator==(const SaveResources & other) const override; + bool operator==(const SaveResources & other) const override; }; } diff --git a/AI/Nullkiller/Goals/StayAtTown.h b/AI/Nullkiller/Goals/StayAtTown.h index 880881386..28aa607a2 100644 --- a/AI/Nullkiller/Goals/StayAtTown.h +++ b/AI/Nullkiller/Goals/StayAtTown.h @@ -26,7 +26,7 @@ namespace Goals public: StayAtTown(const CGTownInstance * town, AIPath & path); - virtual bool operator==(const StayAtTown & other) const override; + bool operator==(const StayAtTown & other) const override; virtual std::string toString() const override; void accept(AIGateway * ai) override; float getMovementWasted() const { return movementWasted; } diff --git a/AI/Nullkiller/Goals/Trade.h b/AI/Nullkiller/Goals/Trade.h index 4360c6700..f29ed8c8f 100644 --- a/AI/Nullkiller/Goals/Trade.h +++ b/AI/Nullkiller/Goals/Trade.h @@ -34,7 +34,7 @@ namespace Goals value = val; objid = Objid; } - virtual bool operator==(const Trade & other) const override; + bool operator==(const Trade & other) const override; }; } diff --git a/AI/Nullkiller/Markers/ArmyUpgrade.h b/AI/Nullkiller/Markers/ArmyUpgrade.h index 1af41a5bf..6f67a3ba4 100644 --- a/AI/Nullkiller/Markers/ArmyUpgrade.h +++ b/AI/Nullkiller/Markers/ArmyUpgrade.h @@ -29,7 +29,7 @@ namespace Goals ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade); ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade); - virtual bool operator==(const ArmyUpgrade & other) const override; + bool operator==(const ArmyUpgrade & other) const override; virtual std::string toString() const override; uint64_t getUpgradeValue() const { return upgradeValue; } diff --git a/AI/Nullkiller/Markers/DefendTown.h b/AI/Nullkiller/Markers/DefendTown.h index 34b8b3427..30ec60d9d 100644 --- a/AI/Nullkiller/Markers/DefendTown.h +++ b/AI/Nullkiller/Markers/DefendTown.h @@ -30,7 +30,7 @@ namespace Goals DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isCounterAttack = false); DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender); - virtual bool operator==(const DefendTown & other) const override; + bool operator==(const DefendTown & other) const override; virtual std::string toString() const override; const HitMapInfo & getTreat() const { return treat; } diff --git a/AI/Nullkiller/Markers/HeroExchange.h b/AI/Nullkiller/Markers/HeroExchange.h index 532d62666..fb6be8af9 100644 --- a/AI/Nullkiller/Markers/HeroExchange.h +++ b/AI/Nullkiller/Markers/HeroExchange.h @@ -28,7 +28,7 @@ namespace Goals sethero(targetHero); } - virtual bool operator==(const HeroExchange & other) const override; + bool operator==(const HeroExchange & other) const override; virtual std::string toString() const override; uint64_t getReinforcementArmyStrength() const; diff --git a/AI/Nullkiller/Markers/UnlockCluster.h b/AI/Nullkiller/Markers/UnlockCluster.h index 3318fc7bc..db0ce7658 100644 --- a/AI/Nullkiller/Markers/UnlockCluster.h +++ b/AI/Nullkiller/Markers/UnlockCluster.h @@ -36,7 +36,7 @@ namespace Goals sethero(pathToCenter.targetHero); } - virtual bool operator==(const UnlockCluster & other) const override; + bool operator==(const UnlockCluster & other) const override; virtual std::string toString() const override; std::shared_ptr getCluster() const { return cluster; } const AIPath & getPathToCenter() { return pathToCenter; } diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 912ac3fe2..a8458939c 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -200,7 +200,7 @@ public: const PathfinderConfig * pathfinderConfig, const CPathfinderHelper * pathfinderHelper) override; - virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override; + void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override; void commit( AIPathNode * destination, diff --git a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h index 3d229a812..2f242e767 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h +++ b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h @@ -34,7 +34,7 @@ namespace AIPathfinding ~AIPathfinderConfig(); - virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; + CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; }; } diff --git a/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h b/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h index b7d596d26..64f48aa0e 100644 --- a/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h +++ b/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h @@ -29,7 +29,7 @@ namespace AIPathfinding public: AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd = DayFlags::NONE); - virtual void execute(const CGHeroInstance * hero) const override; + void execute(const CGHeroInstance * hero) const override; virtual void applyOnDestination( const CGHeroInstance * hero, @@ -38,7 +38,7 @@ namespace AIPathfinding AIPathNode * dstMode, const AIPathNode * srcNode) const override; - virtual bool canAct(const AIPathNode * source) const override; + bool canAct(const AIPathNode * source) const override; virtual std::string toString() const override; }; diff --git a/AI/Nullkiller/Pathfinding/Actions/BattleAction.h b/AI/Nullkiller/Pathfinding/Actions/BattleAction.h index 595ba23cf..d5acb9209 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BattleAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/BattleAction.h @@ -28,7 +28,7 @@ namespace AIPathfinding { } - virtual void execute(const CGHeroInstance * hero) const override; + void execute(const CGHeroInstance * hero) const override; virtual std::string toString() const override; }; diff --git a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h index 5e6ca50d4..cb8d32506 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h +++ b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h @@ -25,7 +25,7 @@ namespace AIPathfinding class SummonBoatAction : public VirtualBoatAction { public: - virtual void execute(const CGHeroInstance * hero) const override; + void execute(const CGHeroInstance * hero) const override; virtual void applyOnDestination( const CGHeroInstance * hero, @@ -34,9 +34,9 @@ namespace AIPathfinding AIPathNode * dstMode, const AIPathNode * srcNode) const override; - virtual bool canAct(const AIPathNode * source) const override; + bool canAct(const AIPathNode * source) const override; - virtual const ChainActor * getActor(const ChainActor * sourceActor) const override; + const ChainActor * getActor(const ChainActor * sourceActor) const override; virtual std::string toString() const override; @@ -56,17 +56,17 @@ namespace AIPathfinding { } - virtual bool canAct(const AIPathNode * source) const override; + bool canAct(const AIPathNode * source) const override; - virtual void execute(const CGHeroInstance * hero) const override; + void execute(const CGHeroInstance * hero) const override; virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override; - virtual const ChainActor * getActor(const ChainActor * sourceActor) const override; + const ChainActor * getActor(const ChainActor * sourceActor) const override; virtual std::string toString() const override; - virtual const CGObjectInstance * targetObject() const override; + const CGObjectInstance * targetObject() const override; }; } diff --git a/AI/Nullkiller/Pathfinding/Actions/QuestAction.h b/AI/Nullkiller/Pathfinding/Actions/QuestAction.h index 15f16a860..467932adc 100644 --- a/AI/Nullkiller/Pathfinding/Actions/QuestAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/QuestAction.h @@ -28,11 +28,11 @@ namespace AIPathfinding { } - virtual bool canAct(const AIPathNode * node) const override; + bool canAct(const AIPathNode * node) const override; virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override; - virtual void execute(const CGHeroInstance * hero) const override; + void execute(const CGHeroInstance * hero) const override; virtual std::string toString() const override; }; diff --git a/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h b/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h index cfb43cc70..32795972f 100644 --- a/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h @@ -29,7 +29,7 @@ namespace AIPathfinding { } - virtual void execute(const CGHeroInstance * hero) const override; + void execute(const CGHeroInstance * hero) const override; virtual std::string toString() const override; }; diff --git a/AI/Nullkiller/Pathfinding/Actors.h b/AI/Nullkiller/Pathfinding/Actors.h index 505f7995b..c723c867f 100644 --- a/AI/Nullkiller/Pathfinding/Actors.h +++ b/AI/Nullkiller/Pathfinding/Actors.h @@ -28,7 +28,7 @@ class HeroExchangeArmy : public CArmedInstance public: TResources armyCost; bool requireBuyArmy; - virtual bool needsLastStack() const override; + bool needsLastStack() const override; std::shared_ptr getActorAction() const; HeroExchangeArmy(): CArmedInstance(nullptr, true), requireBuyArmy(false) {} @@ -126,7 +126,7 @@ public: HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai); protected: - virtual ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const override; + ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const override; }; class ObjectActor : public ChainActor diff --git a/AI/VCAI/Goals/AdventureSpellCast.h b/AI/VCAI/Goals/AdventureSpellCast.h index 28ded9dd1..c8e38004e 100644 --- a/AI/VCAI/Goals/AdventureSpellCast.h +++ b/AI/VCAI/Goals/AdventureSpellCast.h @@ -39,6 +39,6 @@ namespace Goals void accept(VCAI * ai) override; std::string name() const override; std::string completeMessage() const override; - virtual bool operator==(const AdventureSpellCast & other) const override; + bool operator==(const AdventureSpellCast & other) const override; }; } diff --git a/AI/VCAI/Goals/Build.h b/AI/VCAI/Goals/Build.h index c6d972d9f..dc552e146 100644 --- a/AI/VCAI/Goals/Build.h +++ b/AI/VCAI/Goals/Build.h @@ -29,7 +29,7 @@ namespace Goals TSubgoal whatToDoToAchieve() override; bool fulfillsMe(TSubgoal goal) override; - virtual bool operator==(const Build & other) const override + bool operator==(const Build & other) const override { return true; } diff --git a/AI/VCAI/Goals/BuildBoat.h b/AI/VCAI/Goals/BuildBoat.h index 367fa6ea9..8a707d7e9 100644 --- a/AI/VCAI/Goals/BuildBoat.h +++ b/AI/VCAI/Goals/BuildBoat.h @@ -32,6 +32,6 @@ namespace Goals void accept(VCAI * ai) override; std::string name() const override; std::string completeMessage() const override; - virtual bool operator==(const BuildBoat & other) const override; + bool operator==(const BuildBoat & other) const override; }; } diff --git a/AI/VCAI/Goals/BuildThis.h b/AI/VCAI/Goals/BuildThis.h index 9cc86abb8..8abdac811 100644 --- a/AI/VCAI/Goals/BuildThis.h +++ b/AI/VCAI/Goals/BuildThis.h @@ -43,6 +43,6 @@ namespace Goals } TSubgoal whatToDoToAchieve() override; //bool fulfillsMe(TSubgoal goal) override; - virtual bool operator==(const BuildThis & other) const override; + bool operator==(const BuildThis & other) const override; }; } diff --git a/AI/VCAI/Goals/BuyArmy.h b/AI/VCAI/Goals/BuyArmy.h index 55dc97ec4..fbd8126a9 100644 --- a/AI/VCAI/Goals/BuyArmy.h +++ b/AI/VCAI/Goals/BuyArmy.h @@ -36,6 +36,6 @@ namespace Goals TSubgoal whatToDoToAchieve() override; std::string completeMessage() const override; - virtual bool operator==(const BuyArmy & other) const override; + bool operator==(const BuyArmy & other) const override; }; } diff --git a/AI/VCAI/Goals/CGoal.h b/AI/VCAI/Goals/CGoal.h index bef480c55..f2ff5749c 100644 --- a/AI/VCAI/Goals/CGoal.h +++ b/AI/VCAI/Goals/CGoal.h @@ -76,7 +76,7 @@ namespace Goals //h & value & resID & objid & aid & tile & hero & town & bid; } - virtual bool operator==(const AbstractGoal & g) const override + bool operator==(const AbstractGoal & g) const override { if(goalType != g.goalType) return false; diff --git a/AI/VCAI/Goals/ClearWayTo.h b/AI/VCAI/Goals/ClearWayTo.h index 432f1ad79..da812db44 100644 --- a/AI/VCAI/Goals/ClearWayTo.h +++ b/AI/VCAI/Goals/ClearWayTo.h @@ -40,6 +40,6 @@ namespace Goals TGoalVec getAllPossibleSubgoals() override; TSubgoal whatToDoToAchieve() override; bool fulfillsMe(TSubgoal goal) override; - virtual bool operator==(const ClearWayTo & other) const override; + bool operator==(const ClearWayTo & other) const override; }; } diff --git a/AI/VCAI/Goals/CollectRes.h b/AI/VCAI/Goals/CollectRes.h index 70d76d8cc..8a55cae89 100644 --- a/AI/VCAI/Goals/CollectRes.h +++ b/AI/VCAI/Goals/CollectRes.h @@ -35,6 +35,6 @@ namespace Goals TSubgoal whatToDoToAchieve() override; TSubgoal whatToDoToTrade(); bool fulfillsMe(TSubgoal goal) override; //TODO: Trade - virtual bool operator==(const CollectRes & other) const override; + bool operator==(const CollectRes & other) const override; }; } diff --git a/AI/VCAI/Goals/CompleteQuest.h b/AI/VCAI/Goals/CompleteQuest.h index 16335d278..f50cbfe8f 100644 --- a/AI/VCAI/Goals/CompleteQuest.h +++ b/AI/VCAI/Goals/CompleteQuest.h @@ -30,7 +30,7 @@ namespace Goals TSubgoal whatToDoToAchieve() override; std::string name() const override; std::string completeMessage() const override; - virtual bool operator==(const CompleteQuest & other) const override; + bool operator==(const CompleteQuest & other) const override; private: TGoalVec tryCompleteQuest() const; diff --git a/AI/VCAI/Goals/Conquer.h b/AI/VCAI/Goals/Conquer.h index 295930347..ec274d15b 100644 --- a/AI/VCAI/Goals/Conquer.h +++ b/AI/VCAI/Goals/Conquer.h @@ -27,6 +27,6 @@ namespace Goals } TGoalVec getAllPossibleSubgoals() override; TSubgoal whatToDoToAchieve() override; - virtual bool operator==(const Conquer & other) const override; + bool operator==(const Conquer & other) const override; }; } diff --git a/AI/VCAI/Goals/DigAtTile.h b/AI/VCAI/Goals/DigAtTile.h index 963306539..9e426d241 100644 --- a/AI/VCAI/Goals/DigAtTile.h +++ b/AI/VCAI/Goals/DigAtTile.h @@ -36,6 +36,6 @@ namespace Goals return TGoalVec(); } TSubgoal whatToDoToAchieve() override; - virtual bool operator==(const DigAtTile & other) const override; + bool operator==(const DigAtTile & other) const override; }; } diff --git a/AI/VCAI/Goals/Explore.h b/AI/VCAI/Goals/Explore.h index 54e55dcae..a96de7f29 100644 --- a/AI/VCAI/Goals/Explore.h +++ b/AI/VCAI/Goals/Explore.h @@ -46,7 +46,7 @@ namespace Goals TSubgoal whatToDoToAchieve() override; std::string completeMessage() const override; bool fulfillsMe(TSubgoal goal) override; - virtual bool operator==(const Explore & other) const override; + bool operator==(const Explore & other) const override; private: TSubgoal exploreNearestNeighbour(HeroPtr h) const; diff --git a/AI/VCAI/Goals/FindObj.h b/AI/VCAI/Goals/FindObj.h index fad944dec..c1cc74fe7 100644 --- a/AI/VCAI/Goals/FindObj.h +++ b/AI/VCAI/Goals/FindObj.h @@ -42,6 +42,6 @@ namespace Goals } TSubgoal whatToDoToAchieve() override; bool fulfillsMe(TSubgoal goal) override; - virtual bool operator==(const FindObj & other) const override; + bool operator==(const FindObj & other) const override; }; } \ No newline at end of file diff --git a/AI/VCAI/Goals/GatherArmy.h b/AI/VCAI/Goals/GatherArmy.h index 97108df76..213f50dc5 100644 --- a/AI/VCAI/Goals/GatherArmy.h +++ b/AI/VCAI/Goals/GatherArmy.h @@ -33,6 +33,6 @@ namespace Goals TGoalVec getAllPossibleSubgoals() override; TSubgoal whatToDoToAchieve() override; std::string completeMessage() const override; - virtual bool operator==(const GatherArmy & other) const override; + bool operator==(const GatherArmy & other) const override; }; } diff --git a/AI/VCAI/Goals/GatherTroops.h b/AI/VCAI/Goals/GatherTroops.h index ff93ca186..5d5553f93 100644 --- a/AI/VCAI/Goals/GatherTroops.h +++ b/AI/VCAI/Goals/GatherTroops.h @@ -35,7 +35,7 @@ namespace Goals TGoalVec getAllPossibleSubgoals() override; TSubgoal whatToDoToAchieve() override; bool fulfillsMe(TSubgoal goal) override; - virtual bool operator==(const GatherTroops & other) const override; + bool operator==(const GatherTroops & other) const override; private: int getCreaturesCount(const CArmedInstance * army); diff --git a/AI/VCAI/Goals/GetArtOfType.h b/AI/VCAI/Goals/GetArtOfType.h index ba31c2a56..4a69cdb82 100644 --- a/AI/VCAI/Goals/GetArtOfType.h +++ b/AI/VCAI/Goals/GetArtOfType.h @@ -35,6 +35,6 @@ namespace Goals return TGoalVec(); } TSubgoal whatToDoToAchieve() override; - virtual bool operator==(const GetArtOfType & other) const override; + bool operator==(const GetArtOfType & other) const override; }; } diff --git a/AI/VCAI/Goals/Invalid.h b/AI/VCAI/Goals/Invalid.h index cd77ac929..044acfd6c 100644 --- a/AI/VCAI/Goals/Invalid.h +++ b/AI/VCAI/Goals/Invalid.h @@ -33,7 +33,7 @@ namespace Goals return iAmElementar(); } - virtual bool operator==(const Invalid & other) const override + bool operator==(const Invalid & other) const override { return true; } diff --git a/AI/VCAI/Goals/RecruitHero.h b/AI/VCAI/Goals/RecruitHero.h index c381eb2ad..253102a2f 100644 --- a/AI/VCAI/Goals/RecruitHero.h +++ b/AI/VCAI/Goals/RecruitHero.h @@ -33,7 +33,7 @@ namespace Goals TSubgoal whatToDoToAchieve() override; - virtual bool operator==(const RecruitHero & other) const override + bool operator==(const RecruitHero & other) const override { return true; } diff --git a/AI/VCAI/Goals/Trade.h b/AI/VCAI/Goals/Trade.h index c8f64416f..f5bced17f 100644 --- a/AI/VCAI/Goals/Trade.h +++ b/AI/VCAI/Goals/Trade.h @@ -33,6 +33,6 @@ namespace Goals priority = 3; //trading is instant, but picking resources is free } TSubgoal whatToDoToAchieve() override; - virtual bool operator==(const Trade & other) const override; + bool operator==(const Trade & other) const override; }; } diff --git a/AI/VCAI/Goals/VisitHero.h b/AI/VCAI/Goals/VisitHero.h index c209a0abf..bfaa6ffd7 100644 --- a/AI/VCAI/Goals/VisitHero.h +++ b/AI/VCAI/Goals/VisitHero.h @@ -37,6 +37,6 @@ namespace Goals TSubgoal whatToDoToAchieve() override; bool fulfillsMe(TSubgoal goal) override; std::string completeMessage() const override; - virtual bool operator==(const VisitHero & other) const override; + bool operator==(const VisitHero & other) const override; }; } diff --git a/AI/VCAI/Goals/VisitObj.h b/AI/VCAI/Goals/VisitObj.h index 666a9acf0..b92bb4378 100644 --- a/AI/VCAI/Goals/VisitObj.h +++ b/AI/VCAI/Goals/VisitObj.h @@ -27,6 +27,6 @@ namespace Goals TSubgoal whatToDoToAchieve() override; bool fulfillsMe(TSubgoal goal) override; std::string completeMessage() const override; - virtual bool operator==(const VisitObj & other) const override; + bool operator==(const VisitObj & other) const override; }; } diff --git a/AI/VCAI/Goals/VisitTile.h b/AI/VCAI/Goals/VisitTile.h index 334b44c32..50faebd4e 100644 --- a/AI/VCAI/Goals/VisitTile.h +++ b/AI/VCAI/Goals/VisitTile.h @@ -32,6 +32,6 @@ namespace Goals TGoalVec getAllPossibleSubgoals() override; TSubgoal whatToDoToAchieve() override; std::string completeMessage() const override; - virtual bool operator==(const VisitTile & other) const override; + bool operator==(const VisitTile & other) const override; }; } diff --git a/AI/VCAI/Goals/Win.h b/AI/VCAI/Goals/Win.h index 07dfcdee5..e4edaac7b 100644 --- a/AI/VCAI/Goals/Win.h +++ b/AI/VCAI/Goals/Win.h @@ -31,7 +31,7 @@ namespace Goals } TSubgoal whatToDoToAchieve() override; - virtual bool operator==(const Win & other) const override + bool operator==(const Win & other) const override { return true; } diff --git a/AI/VCAI/Pathfinding/AINodeStorage.h b/AI/VCAI/Pathfinding/AINodeStorage.h index 51e32fdcc..4b8118191 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.h +++ b/AI/VCAI/Pathfinding/AINodeStorage.h @@ -99,7 +99,7 @@ public: const PathfinderConfig * pathfinderConfig, const CPathfinderHelper * pathfinderHelper) override; - virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override; + void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override; const AIPathNode * getAINode(const CGPathNode * node) const; void updateAINode(CGPathNode * node, std::function updater); diff --git a/AI/VCAI/Pathfinding/AIPathfinderConfig.h b/AI/VCAI/Pathfinding/AIPathfinderConfig.h index 81ec9722c..076579e12 100644 --- a/AI/VCAI/Pathfinding/AIPathfinderConfig.h +++ b/AI/VCAI/Pathfinding/AIPathfinderConfig.h @@ -30,6 +30,6 @@ namespace AIPathfinding ~AIPathfinderConfig(); - virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; + CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; }; } diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index ee3f47164..2ec4102fe 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -899,14 +899,14 @@ public: { } - virtual bool callTyped() override { return false; } + bool callTyped() override { return false; } - virtual void visitForLobby(CPackForLobby & lobbyPack) override + void visitForLobby(CPackForLobby & lobbyPack) override { handler.visitForLobby(lobbyPack); } - virtual void visitForClient(CPackForClient & clientPack) override + void visitForClient(CPackForClient & clientPack) override { handler.visitForClient(clientPack); } diff --git a/client/Client.h b/client/Client.h index 85893b5b8..5fe64b6b1 100644 --- a/client/Client.h +++ b/client/Client.h @@ -173,7 +173,7 @@ public: void showTeleportDialog(TeleportDialog * iw) override {}; void showObjectWindow(const CGObjectInstance * object, EOpenWindowMode window, const CGHeroInstance * visitor, bool addQuery) override {}; void giveResource(PlayerColor player, GameResID which, int val) override {}; - virtual void giveResources(PlayerColor player, TResources resources) override {}; + void giveResources(PlayerColor player, TResources resources) override {}; void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet & creatures, bool remove) override {}; void takeCreatures(ObjectInstanceID objid, const std::vector & creatures) override {}; diff --git a/client/ClientNetPackVisitors.h b/client/ClientNetPackVisitors.h index 08c0908e2..51812dedc 100644 --- a/client/ClientNetPackVisitors.h +++ b/client/ClientNetPackVisitors.h @@ -118,16 +118,16 @@ public: { } - virtual void visitChangeObjPos(ChangeObjPos & pack) override; - virtual void visitRemoveObject(RemoveObject & pack) override; - virtual void visitTryMoveHero(TryMoveHero & pack) override; - virtual void visitGiveHero(GiveHero & pack) override; - virtual void visitBattleStart(BattleStart & pack) override; - virtual void visitBattleNextRound(BattleNextRound & pack) override; - virtual void visitBattleUpdateGateState(BattleUpdateGateState & pack) override; - virtual void visitBattleResult(BattleResult & pack) override; - virtual void visitBattleStackMoved(BattleStackMoved & pack) override; - virtual void visitBattleAttack(BattleAttack & pack) override; - virtual void visitStartAction(StartAction & pack) override; - virtual void visitSetObjectProperty(SetObjectProperty & pack) override; + void visitChangeObjPos(ChangeObjPos & pack) override; + void visitRemoveObject(RemoveObject & pack) override; + void visitTryMoveHero(TryMoveHero & pack) override; + void visitGiveHero(GiveHero & pack) override; + void visitBattleStart(BattleStart & pack) override; + void visitBattleNextRound(BattleNextRound & pack) override; + void visitBattleUpdateGateState(BattleUpdateGateState & pack) override; + void visitBattleResult(BattleResult & pack) override; + void visitBattleStackMoved(BattleStackMoved & pack) override; + void visitBattleAttack(BattleAttack & pack) override; + void visitStartAction(StartAction & pack) override; + void visitSetObjectProperty(SetObjectProperty & pack) override; }; diff --git a/client/LobbyClientNetPackVisitors.h b/client/LobbyClientNetPackVisitors.h index 7e19e614b..373325573 100644 --- a/client/LobbyClientNetPackVisitors.h +++ b/client/LobbyClientNetPackVisitors.h @@ -32,11 +32,11 @@ public: bool getResult() const { return result; } - virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; - virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbyEndGame(LobbyEndGame & pack) override; - virtual void visitLobbyStartGame(LobbyStartGame & pack) override; - virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override; + void visitLobbyClientConnected(LobbyClientConnected & pack) override; + void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; + void visitLobbyEndGame(LobbyEndGame & pack) override; + void visitLobbyStartGame(LobbyStartGame & pack) override; + void visitLobbyUpdateState(LobbyUpdateState & pack) override; }; class ApplyOnLobbyScreenNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) @@ -51,11 +51,11 @@ public: { } - virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbyChatMessage(LobbyChatMessage & pack) override; - virtual void visitLobbyGuiAction(LobbyGuiAction & pack) override; - virtual void visitLobbyStartGame(LobbyStartGame & pack) override; - virtual void visitLobbyLoadProgress(LobbyLoadProgress & pack) override; - virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override; - virtual void visitLobbyShowMessage(LobbyShowMessage & pack) override; + void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; + void visitLobbyChatMessage(LobbyChatMessage & pack) override; + void visitLobbyGuiAction(LobbyGuiAction & pack) override; + void visitLobbyStartGame(LobbyStartGame & pack) override; + void visitLobbyLoadProgress(LobbyLoadProgress & pack) override; + void visitLobbyUpdateState(LobbyUpdateState & pack) override; + void visitLobbyShowMessage(LobbyShowMessage & pack) override; }; diff --git a/client/widgets/MiscWidgets.h b/client/widgets/MiscWidgets.h index 1eebe87ea..acdfcd2dd 100644 --- a/client/widgets/MiscWidgets.h +++ b/client/widgets/MiscWidgets.h @@ -40,7 +40,7 @@ class CHoverableArea: public virtual CIntObject public: std::string hoverText; - virtual void hover (bool on) override; + void hover (bool on) override; CHoverableArea(); virtual ~CHoverableArea(); diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h index eea665326..d79ed40f3 100644 --- a/client/windows/InfoWindows.h +++ b/client/windows/InfoWindows.h @@ -79,7 +79,7 @@ public: class CRClickPopup : public WindowBase { public: - virtual void close() override; + void close() override; bool isPopupWindow() const override; static std::shared_ptr createCustomInfoWindow(Point position, const CGObjectInstance * specific); diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index 4ea8f5481..a4c416028 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -107,7 +107,7 @@ public: std::string getJsonKey() const override; void registerIcons(const IconRegistar & cb) const override; ArtifactID getId() const override; - virtual const IBonusBearer * getBonusBearer() const override; + const IBonusBearer * getBonusBearer() const override; std::string getDescriptionTranslated() const override; std::string getEventTranslated() const override; diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index f281a8d1f..4bfada15d 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -126,7 +126,7 @@ public: std::string getJsonKey() const override; void registerIcons(const IconRegistar & cb) const override; CreatureID getId() const override; - virtual const IBonusBearer * getBonusBearer() const override; + const IBonusBearer * getBonusBearer() const override; int32_t getAdvMapAmountMin() const override; int32_t getAdvMapAmountMax() const override; diff --git a/lib/CGameInfoCallback.h b/lib/CGameInfoCallback.h index 8de208a9c..2d2065cfc 100644 --- a/lib/CGameInfoCallback.h +++ b/lib/CGameInfoCallback.h @@ -171,7 +171,7 @@ public: virtual void fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out)const; //hero - virtual const CGHeroInstance * getHero(ObjectInstanceID objid) const override; + const CGHeroInstance * getHero(ObjectInstanceID objid) const override; const CGHeroInstance * getHeroWithSubid(int subid) const override; virtual int getHeroCount(PlayerColor player, bool includeGarrisoned) const; virtual bool getHeroInfo(const CGObjectInstance * hero, InfoAboutHero & dest, const CGObjectInstance * selectedObject = nullptr) const; @@ -183,7 +183,7 @@ public: //virtual const CGObjectInstance * getArmyInstance(ObjectInstanceID oid) const; //objects - virtual const CGObjectInstance * getObj(ObjectInstanceID objid, bool verbose = true) const override; + const CGObjectInstance * getObj(ObjectInstanceID objid, bool verbose = true) const override; virtual std::vector getBlockingObjs(int3 pos)const; virtual std::vector getVisitableObjs(int3 pos, bool verbose = true) const override; virtual std::vector getFlaggableObjects(int3 pos) const; diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index 656e72a0d..c3d286455 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -144,26 +144,26 @@ public: virtual std::string getBattleAIName() const = 0; //has to return name of the battle AI to be used //battle interface - virtual void activeStack(const BattleID & battleID, const CStack * stack) override; - virtual void yourTacticPhase(const BattleID & battleID, int distance) override; + void activeStack(const BattleID & battleID, const CStack * stack) override; + void yourTacticPhase(const BattleID & battleID, int distance) override; - virtual void battleNewRound(const BattleID & battleID) override; - virtual void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; - virtual void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed) override; + void battleNewRound(const BattleID & battleID) override; + void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; + void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed) override; virtual void battleStacksAttacked(const BattleID & battleID, const std::vector & bsa, bool ranged) override; - virtual void actionStarted(const BattleID & battleID, const BattleAction &action) override; - virtual void battleNewRoundFirst(const BattleID & battleID) override; - virtual void actionFinished(const BattleID & battleID, const BattleAction &action) override; - virtual void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override; + void actionStarted(const BattleID & battleID, const BattleAction &action) override; + void battleNewRoundFirst(const BattleID & battleID) override; + void actionFinished(const BattleID & battleID, const BattleAction &action) override; + void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override; virtual void battleObstaclesChanged(const BattleID & battleID, const std::vector & obstacles) override; virtual void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector dest, int distance, bool teleport) override; - virtual void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; - virtual void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; - virtual void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override; + void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; + void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; + void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override; virtual void battleUnitsChanged(const BattleID & battleID, const std::vector & units) override; - virtual void saveGame(BinarySerializer & h) override; - virtual void loadGame(BinaryDeserializer & h) override; + void saveGame(BinarySerializer & h) override; + void loadGame(BinaryDeserializer & h) override; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/bonuses/Updaters.h b/lib/bonuses/Updaters.h index 05219d2e8..f96c552e9 100644 --- a/lib/bonuses/Updaters.h +++ b/lib/bonuses/Updaters.h @@ -49,7 +49,7 @@ public: std::shared_ptr createUpdatedBonus(const std::shared_ptr & b, const CBonusSystemNode & context) const override; virtual std::string toString() const override; - virtual JsonNode toJsonNode() const override; + JsonNode toJsonNode() const override; }; class DLL_LINKAGE TimesHeroLevelUpdater : public IUpdater @@ -62,7 +62,7 @@ public: std::shared_ptr createUpdatedBonus(const std::shared_ptr & b, const CBonusSystemNode & context) const override; virtual std::string toString() const override; - virtual JsonNode toJsonNode() const override; + JsonNode toJsonNode() const override; }; class DLL_LINKAGE TimesStackLevelUpdater : public IUpdater @@ -75,7 +75,7 @@ public: std::shared_ptr createUpdatedBonus(const std::shared_ptr & b, const CBonusSystemNode & context) const override; virtual std::string toString() const override; - virtual JsonNode toJsonNode() const override; + JsonNode toJsonNode() const override; }; class DLL_LINKAGE ArmyMovementUpdater : public IUpdater @@ -98,7 +98,7 @@ public: std::shared_ptr createUpdatedBonus(const std::shared_ptr & b, const CBonusSystemNode & context) const override; virtual std::string toString() const override; - virtual JsonNode toJsonNode() const override; + JsonNode toJsonNode() const override; }; class DLL_LINKAGE OwnerUpdater : public IUpdater @@ -111,7 +111,7 @@ public: std::shared_ptr createUpdatedBonus(const std::shared_ptr& b, const CBonusSystemNode& context) const override; virtual std::string toString() const override; - virtual JsonNode toJsonNode() const override; + JsonNode toJsonNode() const override; }; VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index b75a31be7..6b74fc402 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -424,7 +424,7 @@ class DLL_LINKAGE CGTerrainPatch : public CGObjectInstance public: using CGObjectInstance::CGObjectInstance; - virtual bool isTile2Terrain() const override + bool isTile2Terrain() const override { return true; } diff --git a/lib/networkPacks/PacksForLobby.h b/lib/networkPacks/PacksForLobby.h index ff7c242f3..e5a81cc5f 100644 --- a/lib/networkPacks/PacksForLobby.h +++ b/lib/networkPacks/PacksForLobby.h @@ -29,7 +29,7 @@ struct DLL_LINKAGE CLobbyPackToPropagate : public CPackForLobby struct DLL_LINKAGE CLobbyPackToServer : public CPackForLobby { - virtual bool isForServer() const override; + bool isForServer() const override; }; struct DLL_LINKAGE LobbyClientConnected : public CLobbyPackToPropagate diff --git a/lib/pathfinder/CGPathNode.h b/lib/pathfinder/CGPathNode.h index 15363f419..28ee2f920 100644 --- a/lib/pathfinder/CGPathNode.h +++ b/lib/pathfinder/CGPathNode.h @@ -233,7 +233,7 @@ struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo CDestinationNodeInfo(); - virtual void setNode(CGameState * gs, CGPathNode * n) override; + void setNode(CGameState * gs, CGPathNode * n) override; virtual bool isBetterWay() const; }; diff --git a/lib/pathfinder/NodeStorage.h b/lib/pathfinder/NodeStorage.h index eddb23201..afe19b10e 100644 --- a/lib/pathfinder/NodeStorage.h +++ b/lib/pathfinder/NodeStorage.h @@ -46,7 +46,7 @@ public: const PathfinderConfig * pathfinderConfig, const CPathfinderHelper * pathfinderHelper) override; - virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override; + void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/PathfinderOptions.h b/lib/pathfinder/PathfinderOptions.h index 4a1d4b985..cb7364518 100644 --- a/lib/pathfinder/PathfinderOptions.h +++ b/lib/pathfinder/PathfinderOptions.h @@ -104,7 +104,7 @@ public: SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero); virtual ~SingleHeroPathfinderConfig(); - virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; + CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; static std::vector> buildRuleSet(); }; diff --git a/lib/rmg/modificators/Modificator.h b/lib/rmg/modificators/Modificator.h index ef5be49bc..88f5c7f51 100644 --- a/lib/rmg/modificators/Modificator.h +++ b/lib/rmg/modificators/Modificator.h @@ -40,7 +40,7 @@ public: Modificator() = delete; Modificator(Zone & zone, RmgMap & map, CMapGenerator & generator); - virtual void init() {/*override to add dependencies*/} + void init() {/*override to add dependencies*/} virtual char dump(const int3 &); virtual ~Modificator() = default; diff --git a/scripting/lua/LuaSpellEffect.h b/scripting/lua/LuaSpellEffect.h index effb620e7..54b11d4c2 100644 --- a/scripting/lua/LuaSpellEffect.h +++ b/scripting/lua/LuaSpellEffect.h @@ -35,7 +35,7 @@ public: LuaSpellEffectFactory(const Script * script_); virtual ~LuaSpellEffectFactory(); - virtual Effect * create() const override; + Effect * create() const override; private: const Script * script; diff --git a/scripting/lua/api/BonusSystem.h b/scripting/lua/api/BonusSystem.h index 657243670..ebaabc1e0 100644 --- a/scripting/lua/api/BonusSystem.h +++ b/scripting/lua/api/BonusSystem.h @@ -44,7 +44,7 @@ public: static int toJsonNode(lua_State * L); protected: - virtual void adjustStaticTable(lua_State * L) const override; + void adjustStaticTable(lua_State * L) const override; }; class BonusListProxy : public SharedWrapper @@ -56,7 +56,7 @@ public: static std::shared_ptr index(std::shared_ptr self, int key); protected: - virtual void adjustMetatable(lua_State * L) const override; + void adjustMetatable(lua_State * L) const override; }; class BonusBearerProxy : public OpaqueWrapper diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 8f4ecaf75..e7d11b75b 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -417,14 +417,14 @@ public: { } - virtual bool callTyped() override { return false; } + bool callTyped() override { return false; } - virtual void visitForLobby(CPackForLobby & packForLobby) override + void visitForLobby(CPackForLobby & packForLobby) override { handler.handleReceivedPack(std::unique_ptr(&packForLobby)); } - virtual void visitForServer(CPackForServer & serverPack) override + void visitForServer(CPackForServer & serverPack) override { if (gh) gh->handleReceivedPack(&serverPack); @@ -432,7 +432,7 @@ public: logNetwork->error("Received pack for game server while in lobby!"); } - virtual void visitForClient(CPackForClient & clientPack) override + void visitForClient(CPackForClient & clientPack) override { } }; diff --git a/server/LobbyNetPackVisitors.h b/server/LobbyNetPackVisitors.h index 6304f561a..9f22488e9 100644 --- a/server/LobbyNetPackVisitors.h +++ b/server/LobbyNetPackVisitors.h @@ -28,15 +28,15 @@ public: return result; } - virtual void visitForLobby(CPackForLobby & pack) override; - virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; - virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbyEndGame(LobbyEndGame & pack) override; - virtual void visitLobbyStartGame(LobbyStartGame & pack) override; - virtual void visitLobbyChangeHost(LobbyChangeHost & pack) override; - virtual void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override; - virtual void visitLobbyChatMessage(LobbyChatMessage & pack) override; - virtual void visitLobbyGuiAction(LobbyGuiAction & pack) override; + void visitForLobby(CPackForLobby & pack) override; + void visitLobbyClientConnected(LobbyClientConnected & pack) override; + void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; + void visitLobbyEndGame(LobbyEndGame & pack) override; + void visitLobbyStartGame(LobbyStartGame & pack) override; + void visitLobbyChangeHost(LobbyChangeHost & pack) override; + void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override; + void visitLobbyChatMessage(LobbyChatMessage & pack) override; + void visitLobbyGuiAction(LobbyGuiAction & pack) override; }; class ApplyOnServerAfterAnnounceNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) @@ -50,12 +50,12 @@ public: { } - virtual void visitForLobby(CPackForLobby & pack) override; - virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; - virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbyEndGame(LobbyEndGame & pack) override; - virtual void visitLobbyStartGame(LobbyStartGame & pack) override; - virtual void visitLobbyChangeHost(LobbyChangeHost & pack) override; + void visitForLobby(CPackForLobby & pack) override; + void visitLobbyClientConnected(LobbyClientConnected & pack) override; + void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; + void visitLobbyEndGame(LobbyEndGame & pack) override; + void visitLobbyStartGame(LobbyStartGame & pack) override; + void visitLobbyChangeHost(LobbyChangeHost & pack) override; }; class ApplyOnServerNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) @@ -75,21 +75,21 @@ public: return result; } - virtual void visitLobbyClientConnected(LobbyClientConnected & pack) override; - virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; - virtual void visitLobbySetMap(LobbySetMap & pack) override; - virtual void visitLobbySetCampaign(LobbySetCampaign & pack) override; - virtual void visitLobbySetCampaignMap(LobbySetCampaignMap & pack) override; - virtual void visitLobbySetCampaignBonus(LobbySetCampaignBonus & pack) override; - virtual void visitLobbyEndGame(LobbyEndGame & pack) override; - virtual void visitLobbyStartGame(LobbyStartGame & pack) override; - virtual void visitLobbyChangeHost(LobbyChangeHost & pack) override; - virtual void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override; - virtual void visitLobbySetPlayer(LobbySetPlayer & pack) override; - virtual void visitLobbySetPlayerName(LobbySetPlayerName & pack) override; - virtual void visitLobbySetTurnTime(LobbySetTurnTime & pack) override; - virtual void visitLobbySetExtraOptions(LobbySetExtraOptions & pack) override; - virtual void visitLobbySetSimturns(LobbySetSimturns & pack) override; - virtual void visitLobbySetDifficulty(LobbySetDifficulty & pack) override; - virtual void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) override; + void visitLobbyClientConnected(LobbyClientConnected & pack) override; + void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) override; + void visitLobbySetMap(LobbySetMap & pack) override; + void visitLobbySetCampaign(LobbySetCampaign & pack) override; + void visitLobbySetCampaignMap(LobbySetCampaignMap & pack) override; + void visitLobbySetCampaignBonus(LobbySetCampaignBonus & pack) override; + void visitLobbyEndGame(LobbyEndGame & pack) override; + void visitLobbyStartGame(LobbyStartGame & pack) override; + void visitLobbyChangeHost(LobbyChangeHost & pack) override; + void visitLobbyChangePlayerOption(LobbyChangePlayerOption & pack) override; + void visitLobbySetPlayer(LobbySetPlayer & pack) override; + void visitLobbySetPlayerName(LobbySetPlayerName & pack) override; + void visitLobbySetTurnTime(LobbySetTurnTime & pack) override; + void visitLobbySetExtraOptions(LobbySetExtraOptions & pack) override; + void visitLobbySetSimturns(LobbySetSimturns & pack) override; + void visitLobbySetDifficulty(LobbySetDifficulty & pack) override; + void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) override; }; diff --git a/server/ServerNetPackVisitors.h b/server/ServerNetPackVisitors.h index 487af47de..c2cc0de42 100644 --- a/server/ServerNetPackVisitors.h +++ b/server/ServerNetPackVisitors.h @@ -28,34 +28,34 @@ public: return result; } - virtual void visitSaveGame(SaveGame & pack) override; - virtual void visitGamePause(GamePause & pack) override; - virtual void visitEndTurn(EndTurn & pack) override; - virtual void visitDismissHero(DismissHero & pack) override; - virtual void visitMoveHero(MoveHero & pack) override; - virtual void visitCastleTeleportHero(CastleTeleportHero & pack) override; - virtual void visitArrangeStacks(ArrangeStacks & pack) override; - virtual void visitBulkMoveArmy(BulkMoveArmy & pack) override; - virtual void visitBulkSplitStack(BulkSplitStack & pack) override; - virtual void visitBulkMergeStacks(BulkMergeStacks & pack) override; - virtual void visitBulkSmartSplitStack(BulkSmartSplitStack & pack) override; - virtual void visitDisbandCreature(DisbandCreature & pack) override; - virtual void visitBuildStructure(BuildStructure & pack) override; - virtual void visitRecruitCreatures(RecruitCreatures & pack) override; - virtual void visitUpgradeCreature(UpgradeCreature & pack) override; - virtual void visitGarrisonHeroSwap(GarrisonHeroSwap & pack) override; - virtual void visitExchangeArtifacts(ExchangeArtifacts & pack) override; - virtual void visitBulkExchangeArtifacts(BulkExchangeArtifacts & pack) override; - virtual void visitAssembleArtifacts(AssembleArtifacts & pack) override; - virtual void visitEraseArtifactByClient(EraseArtifactByClient & pack) override; - virtual void visitBuyArtifact(BuyArtifact & pack) override; - virtual void visitTradeOnMarketplace(TradeOnMarketplace & pack) override; - virtual void visitSetFormation(SetFormation & pack) override; - virtual void visitHireHero(HireHero & pack) override; - virtual void visitBuildBoat(BuildBoat & pack) override; - virtual void visitQueryReply(QueryReply & pack) override; - virtual void visitMakeAction(MakeAction & pack) override; - virtual void visitDigWithHero(DigWithHero & pack) override; - virtual void visitCastAdvSpell(CastAdvSpell & pack) override; - virtual void visitPlayerMessage(PlayerMessage & pack) override; + void visitSaveGame(SaveGame & pack) override; + void visitGamePause(GamePause & pack) override; + void visitEndTurn(EndTurn & pack) override; + void visitDismissHero(DismissHero & pack) override; + void visitMoveHero(MoveHero & pack) override; + void visitCastleTeleportHero(CastleTeleportHero & pack) override; + void visitArrangeStacks(ArrangeStacks & pack) override; + void visitBulkMoveArmy(BulkMoveArmy & pack) override; + void visitBulkSplitStack(BulkSplitStack & pack) override; + void visitBulkMergeStacks(BulkMergeStacks & pack) override; + void visitBulkSmartSplitStack(BulkSmartSplitStack & pack) override; + void visitDisbandCreature(DisbandCreature & pack) override; + void visitBuildStructure(BuildStructure & pack) override; + void visitRecruitCreatures(RecruitCreatures & pack) override; + void visitUpgradeCreature(UpgradeCreature & pack) override; + void visitGarrisonHeroSwap(GarrisonHeroSwap & pack) override; + void visitExchangeArtifacts(ExchangeArtifacts & pack) override; + void visitBulkExchangeArtifacts(BulkExchangeArtifacts & pack) override; + void visitAssembleArtifacts(AssembleArtifacts & pack) override; + void visitEraseArtifactByClient(EraseArtifactByClient & pack) override; + void visitBuyArtifact(BuyArtifact & pack) override; + void visitTradeOnMarketplace(TradeOnMarketplace & pack) override; + void visitSetFormation(SetFormation & pack) override; + void visitHireHero(HireHero & pack) override; + void visitBuildBoat(BuildBoat & pack) override; + void visitQueryReply(QueryReply & pack) override; + void visitMakeAction(MakeAction & pack) override; + void visitDigWithHero(DigWithHero & pack) override; + void visitCastAdvSpell(CastAdvSpell & pack) override; + void visitPlayerMessage(PlayerMessage & pack) override; }; diff --git a/server/queries/BattleQueries.h b/server/queries/BattleQueries.h index 52d8e94ee..871b62e5d 100644 --- a/server/queries/BattleQueries.h +++ b/server/queries/BattleQueries.h @@ -28,10 +28,10 @@ public: CBattleQuery(CGameHandler * owner); CBattleQuery(CGameHandler * owner, const IBattleInfo * Bi); //TODO - virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; - virtual bool blocksPack(const CPack *pack) const override; - virtual void onRemoval(PlayerColor color) override; - virtual void onExposure(QueryPtr topQuery) override; + void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; + bool blocksPack(const CPack *pack) const override; + void onRemoval(PlayerColor color) override; + void onExposure(QueryPtr topQuery) override; }; class CBattleDialogQuery : public CDialogQuery @@ -41,5 +41,5 @@ public: const IBattleInfo * bi; - virtual void onRemoval(PlayerColor color) override; + void onRemoval(PlayerColor color) override; }; diff --git a/server/queries/CQuery.h b/server/queries/CQuery.h index cf6e18254..5773ecadd 100644 --- a/server/queries/CQuery.h +++ b/server/queries/CQuery.h @@ -68,8 +68,8 @@ class CDialogQuery : public CQuery { public: CDialogQuery(CGameHandler * owner); - virtual bool endsByPlayerAnswer() const override; - virtual bool blocksPack(const CPack *pack) const override; + bool endsByPlayerAnswer() const override; + bool blocksPack(const CPack *pack) const override; void setReply(std::optional reply) override; protected: std::optional answer; diff --git a/server/queries/MapQueries.h b/server/queries/MapQueries.h index 5668dad02..7a6cabf3c 100644 --- a/server/queries/MapQueries.h +++ b/server/queries/MapQueries.h @@ -46,9 +46,9 @@ public: CObjectVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero, int3 Tile); - virtual bool blocksPack(const CPack *pack) const override; - virtual void onRemoval(PlayerColor color) override; - virtual void onExposure(QueryPtr topQuery) override; + bool blocksPack(const CPack *pack) const override; + void onRemoval(PlayerColor color) override; + void onExposure(QueryPtr topQuery) override; }; //Created when hero attempts move and something happens @@ -60,11 +60,11 @@ public: bool visitDestAfterVictory; //if hero moved to guarded tile and it should be visited once guard is defeated const CGHeroInstance *hero; - virtual void onExposure(QueryPtr topQuery) override; + void onExposure(QueryPtr topQuery) override; CHeroMovementQuery(CGameHandler * owner, const TryMoveHero & Tmh, const CGHeroInstance * Hero, bool VisitDestAfterVictory = false); - virtual void onAdding(PlayerColor color) override; - virtual void onRemoval(PlayerColor color) override; + void onAdding(PlayerColor color) override; + void onRemoval(PlayerColor color) override; }; class CGarrisonDialogQuery : public CDialogQuery //used also for hero exchange dialogs @@ -73,8 +73,8 @@ public: std::array exchangingArmies; CGarrisonDialogQuery(CGameHandler * owner, const CArmedInstance *up, const CArmedInstance *down); - virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; - virtual bool blocksPack(const CPack *pack) const override; + void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; + bool blocksPack(const CPack *pack) const override; }; //yes/no and component selection dialogs @@ -85,7 +85,7 @@ public: CBlockingDialogQuery(CGameHandler * owner, const BlockingDialog &bd); - virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; + void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; }; class OpenWindowQuery : public CDialogQuery @@ -105,7 +105,7 @@ public: CTeleportDialogQuery(CGameHandler * owner, const TeleportDialog &td); - virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; + void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; }; class CHeroLevelUpDialogQuery : public CDialogQuery @@ -113,8 +113,8 @@ class CHeroLevelUpDialogQuery : public CDialogQuery public: CHeroLevelUpDialogQuery(CGameHandler * owner, const HeroLevelUp &Hlu, const CGHeroInstance * Hero); - virtual void onRemoval(PlayerColor color) override; - virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; + void onRemoval(PlayerColor color) override; + void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; HeroLevelUp hlu; const CGHeroInstance * hero; @@ -125,8 +125,8 @@ class CCommanderLevelUpDialogQuery : public CDialogQuery public: CCommanderLevelUpDialogQuery(CGameHandler * owner, const CommanderLevelUp &Clu, const CGHeroInstance * Hero); - virtual void onRemoval(PlayerColor color) override; - virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; + void onRemoval(PlayerColor color) override; + void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; CommanderLevelUp clu; const CGHeroInstance * hero; diff --git a/test/CVcmiTestConfig.h b/test/CVcmiTestConfig.h index 5a1fb007d..9052fa80b 100644 --- a/test/CVcmiTestConfig.h +++ b/test/CVcmiTestConfig.h @@ -15,6 +15,6 @@ class CVcmiTestConfig : public ::testing::Environment { public: - virtual void SetUp() override; - virtual void TearDown() override; + void SetUp() override; + void TearDown() override; }; From 54b44aa5e8d71b63fe1083a0205963721e174557 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Sat, 10 Feb 2024 20:45:15 +0100 Subject: [PATCH 96/98] Re-add `virtual` that was accidentally removed --- lib/rmg/modificators/Modificator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rmg/modificators/Modificator.h b/lib/rmg/modificators/Modificator.h index 88f5c7f51..ef5be49bc 100644 --- a/lib/rmg/modificators/Modificator.h +++ b/lib/rmg/modificators/Modificator.h @@ -40,7 +40,7 @@ public: Modificator() = delete; Modificator(Zone & zone, RmgMap & map, CMapGenerator & generator); - void init() {/*override to add dependencies*/} + virtual void init() {/*override to add dependencies*/} virtual char dump(const int3 &); virtual ~Modificator() = default; From 38ba42ef7a9a257af77b06bfb8957cafd34d0fe0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 11 Feb 2024 16:26:27 +0200 Subject: [PATCH 97/98] Rename 'c' to 'logicConnection' --- client/CServerHandler.cpp | 38 +++++++++++++++++----------------- client/CServerHandler.h | 3 ++- client/Client.cpp | 10 ++++----- client/NetPacksClient.cpp | 2 +- client/NetPacksLobbyClient.cpp | 14 ++++++------- 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 43a65a9f7..2899906a9 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -170,7 +170,7 @@ void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen serverMode = newServerMode; mapToStart = nullptr; th = std::make_unique(); - c.reset(); + logicConnection.reset(); si = std::make_shared(); playerNames.clear(); si->difficulty = 1; @@ -329,9 +329,9 @@ void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netCon getGlobalLobby().sendProxyConnectionLogin(netConnection); } - c = std::make_shared(netConnection); - c->uuid = uuid; - c->enterLobbyConnectionMode(); + logicConnection = std::make_shared(netConnection); + logicConnection->uuid = uuid; + logicConnection->enterLobbyConnectionMode(); sendClientConnecting(); } @@ -344,22 +344,22 @@ void CServerHandler::applyPackOnLobbyScreen(CPackForLobby & pack) std::set CServerHandler::getHumanColors() { - return clientHumanColors(c->connectionID); + return clientHumanColors(logicConnection->connectionID); } PlayerColor CServerHandler::myFirstColor() const { - return clientFirstColor(c->connectionID); + return clientFirstColor(logicConnection->connectionID); } bool CServerHandler::isMyColor(PlayerColor color) const { - return isClientColor(c->connectionID, color); + return isClientColor(logicConnection->connectionID, color); } ui8 CServerHandler::myFirstId() const { - return clientFirstId(c->connectionID); + return clientFirstId(logicConnection->connectionID); } EClientState CServerHandler::getState() const @@ -379,12 +379,12 @@ bool CServerHandler::isServerLocal() const bool CServerHandler::isHost() const { - return c && hostClientId == c->connectionID; + return logicConnection && hostClientId == logicConnection->connectionID; } bool CServerHandler::isGuest() const { - return !c || hostClientId != c->connectionID; + return !logicConnection || hostClientId != logicConnection->connectionID; } const std::string & CServerHandler::getLocalHostname() const @@ -438,7 +438,7 @@ void CServerHandler::sendClientDisconnecting() setState(EClientState::DISCONNECTING); mapToStart = nullptr; LobbyClientDisconnected lcd; - lcd.clientId = c->connectionID; + lcd.clientId = logicConnection->connectionID; logNetwork->info("Connection has been requested to be closed."); if(isServerLocal()) { @@ -452,7 +452,7 @@ void CServerHandler::sendClientDisconnecting() sendLobbyPack(lcd); networkConnection->close(); networkConnection.reset(); - c.reset(); + logicConnection.reset(); } void CServerHandler::setCampaignState(std::shared_ptr newCampaign) @@ -684,7 +684,7 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta throw std::runtime_error("Invalid mode"); } // After everything initialized we can accept CPackToClient netpacks - c->enterGameplayConnectionMode(client->gameState()); + logicConnection->enterGameplayConnectionMode(client->gameState()); setState(EClientState::GAMEPLAY); } @@ -714,7 +714,7 @@ void CServerHandler::restartGameplay() client->endGame(); client.reset(); - c->enterLobbyConnectionMode(); + logicConnection->enterLobbyConnectionMode(); } void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared_ptr cs) @@ -798,7 +798,7 @@ ELoadMode CServerHandler::getLoadMode() return ELoadMode::CAMPAIGN; for(auto pn : playerNames) { - if(pn.second.connection != c->connectionID) + if(pn.second.connection != logicConnection->connectionID) return ELoadMode::MULTI; } if(howManyPlayerInterfaces() > 1) //this condition will work for hotseat mode OR multiplayer with allowed more than 1 color per player to control @@ -892,7 +892,7 @@ void CServerHandler::onPacketReceived(const std::shared_ptr return; } - CPack * pack = c->retrievePack(message); + CPack * pack = logicConnection->retrievePack(message); ServerHandlerCPackVisitor visitor(*this); pack->visit(visitor); } @@ -921,7 +921,7 @@ void CServerHandler::onDisconnected(const std::shared_ptr & else { LobbyClientDisconnected lcd; - lcd.clientId = c->connectionID; + lcd.clientId = logicConnection->connectionID; applyPackOnLobbyScreen(lcd); } @@ -995,7 +995,7 @@ void CServerHandler::threadRunServer(bool connectToLobby) void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const { if(getState() != EClientState::STARTING) - c->sendPack(&pack); + logicConnection->sendPack(&pack); } bool CServerHandler::inLobbyRoom() const @@ -1005,5 +1005,5 @@ bool CServerHandler::inLobbyRoom() const bool CServerHandler::inGame() const { - return c != nullptr; + return logicConnection != nullptr; } diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 4ec530209..59573676d 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -128,7 +128,8 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor bool isServerLocal() const; public: - std::shared_ptr c; + /// High-level connection overlay that is capable of (de)serializing network data + std::shared_ptr logicConnection; //////////////////// // FIXME: Bunch of crutches to glue it all together diff --git a/client/Client.cpp b/client/Client.cpp index a68608fee..9ff148e9e 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -298,7 +298,7 @@ void CClient::serialize(BinaryDeserializer & h) bool shouldResetInterface = true; // Client no longer handle this player at all - if(!vstd::contains(CSH->getAllClientPlayers(CSH->c->connectionID), pid)) + if(!vstd::contains(CSH->getAllClientPlayers(CSH->logicConnection->connectionID), pid)) { logGlobal->trace("Player %s is not belong to this client. Destroying interface", pid); } @@ -398,7 +398,7 @@ void CClient::initPlayerEnvironments() { playerEnvironments.clear(); - auto allPlayers = CSH->getAllClientPlayers(CSH->c->connectionID); + auto allPlayers = CSH->getAllClientPlayers(CSH->logicConnection->connectionID); bool hasHumanPlayer = false; for(auto & color : allPlayers) { @@ -428,7 +428,7 @@ void CClient::initPlayerInterfaces() for(auto & playerInfo : gs->scenarioOps->playerInfos) { PlayerColor color = playerInfo.first; - if(!vstd::contains(CSH->getAllClientPlayers(CSH->c->connectionID), color)) + if(!vstd::contains(CSH->getAllClientPlayers(CSH->logicConnection->connectionID), color)) continue; if(!vstd::contains(playerint, color)) @@ -458,7 +458,7 @@ void CClient::initPlayerInterfaces() installNewPlayerInterface(std::make_shared(PlayerColor::SPECTATOR), PlayerColor::SPECTATOR, true); } - if(CSH->getAllClientPlayers(CSH->c->connectionID).count(PlayerColor::NEUTRAL)) + if(CSH->getAllClientPlayers(CSH->logicConnection->connectionID).count(PlayerColor::NEUTRAL)) installNewBattleInterface(CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String()), PlayerColor::NEUTRAL); logNetwork->trace("Initialized player interfaces %d ms", CSH->th->getDiff()); @@ -545,7 +545,7 @@ int CClient::sendRequest(const CPackForServer * request, PlayerColor player) waitingRequest.pushBack(requestID); request->requestID = requestID; request->player = player; - CSH->c->sendPack(request); + CSH->logicConnection->sendPack(request); if(vstd::contains(playerint, player)) playerint[player]->requestSent(request, requestID); diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index e22cb04be..d01890f37 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -424,7 +424,7 @@ void ApplyClientNetPackVisitor::visitPlayerReinitInterface(PlayerReinitInterface cl.initPlayerEnvironments(); initInterfaces(); } - else if(pack.playerConnectionId == CSH->c->connectionID) + else if(pack.playerConnectionId == CSH->logicConnection->connectionID) { plSettings.connectedPlayerIDs.insert(pack.playerConnectionId); cl.playerint.clear(); diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index 6ea57b941..6b7108527 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -40,9 +40,9 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon result = false; // Check if it's LobbyClientConnected for our client - if(pack.uuid == handler.c->uuid) + if(pack.uuid == handler.logicConnection->uuid) { - handler.c->connectionID = pack.clientId; + handler.logicConnection->connectionID = pack.clientId; if(handler.mapToStart) { handler.setMapInfo(handler.mapToStart); @@ -79,7 +79,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) { - if(pack.clientId != handler.c->connectionID) + if(pack.clientId != handler.logicConnection->connectionID) { result = false; return; @@ -145,20 +145,20 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) { handler.client = std::make_unique(); - handler.c->enterLobbyConnectionMode(); - handler.c->setCallback(handler.client.get()); + handler.logicConnection->enterLobbyConnectionMode(); + handler.logicConnection->setCallback(handler.client.get()); } void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) { - if(pack.clientId != -1 && pack.clientId != handler.c->connectionID) + if(pack.clientId != -1 && pack.clientId != handler.logicConnection->connectionID) { result = false; return; } handler.setState(EClientState::STARTING); - if(handler.si->mode != EStartMode::LOAD_GAME || pack.clientId == handler.c->connectionID) + if(handler.si->mode != EStartMode::LOAD_GAME || pack.clientId == handler.logicConnection->connectionID) { auto modeBackup = handler.si->mode; handler.si = pack.initializedStartInfo; From 7790acae3a681c636fab731fc819eda824acd8d4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 11 Feb 2024 17:31:30 +0200 Subject: [PATCH 98/98] Fix save compatiblity with 1.4 --- lib/mapping/CMapHeader.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index 3be7fc58e..4f72ec311 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -264,7 +264,12 @@ public: h & width; h & height; h & twoLevel; - h & difficulty; + // FIXME: we should serialize enum's according to their underlying type + // should be fixed when we are making breaking change to save compatiblity + static_assert(Handler::Version::MINIMAL < Handler::Version::RELEASE_143); + uint8_t difficultyInteger = static_cast(difficulty); + h & difficultyInteger; + difficulty = static_cast(difficultyInteger); h & levelLimit; h & areAnyPlayers; h & players;