From 5e36ef92c75b9028dc1a3e9dbfa5bc256dcb436e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Tue, 31 Oct 2023 18:21:50 +0100 Subject: [PATCH 001/250] Support for "selectAll" reward --- lib/mapObjects/CRewardableObject.cpp | 36 ++++++++++++++++++++++++++++ lib/mapObjects/CRewardableObject.h | 2 ++ lib/rewardable/Configuration.h | 3 ++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 4ec6783e0..7b03c8093 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -50,6 +50,38 @@ void CRewardableObject::selectRewardWthMessage(const CGHeroInstance * contextHer sd.text = dialog; sd.components = loadComponents(contextHero, rewardIndices); cb->showBlockingDialog(&sd); + +} + +void CRewardableObject::grantAllRewardsWthMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndices, bool markAsVisit) const +{ + // TODO: A single message for all rewards? + if (rewardIndices.empty()) + return; + + auto index = rewardIndices.front(); + auto vi = configuration.info.at(index); + + logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString()); + // show message only if it is not empty or in infobox + if (configuration.infoWindowType != EInfoWindowMode::MODAL || !vi.message.toString().empty()) + { + InfoWindow iw; + iw.player = contextHero->tempOwner; + iw.text = vi.message; + iw.components = loadComponents(contextHero, rewardIndices); + iw.type = configuration.infoWindowType; + if(!iw.components.empty() || !iw.text.toString().empty()) + cb->showInfoDialog(&iw); + } + // grant reward afterwards. Note that it may remove object + if(markAsVisit) + markAsVisited(contextHero); + + for (auto index : rewardIndices) + { + grantReward(index, contextHero); + } } std::vector CRewardableObject::loadComponents(const CGHeroInstance * contextHero, const std::vector & rewardIndices) const @@ -116,6 +148,10 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const case Rewardable::SELECT_RANDOM: // give random grantRewardWithMessage(h, *RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator()), true); break; + case Rewardable::SELECT_ALL: // grant all possible + auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT); + grantAllRewardsWthMessage(h, rewards, true); + break; } break; } diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h index 3d9484dca..ed5673e49 100644 --- a/lib/mapObjects/CRewardableObject.h +++ b/lib/mapObjects/CRewardableObject.h @@ -37,6 +37,8 @@ protected: virtual void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const; virtual void selectRewardWthMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndices, const MetaString & dialog) const; + virtual void grantAllRewardsWthMessage(const CGHeroInstance * contextHero, const std::vector& rewardIndices, bool markAsVisit) const; + std::vector loadComponents(const CGHeroInstance * contextHero, const std::vector & rewardIndices) const; std::string getDisplayTextImpl(PlayerColor player, const CGHeroInstance * hero, bool includeDescription) const; diff --git a/lib/rewardable/Configuration.h b/lib/rewardable/Configuration.h index 6ea12df6a..602142343 100644 --- a/lib/rewardable/Configuration.h +++ b/lib/rewardable/Configuration.h @@ -36,6 +36,7 @@ enum ESelectMode SELECT_FIRST, // first reward that matches limiters SELECT_PLAYER, // player can select from all allowed rewards SELECT_RANDOM, // one random reward from all mathing limiters + SELECT_ALL // grant all rewards that match limiters }; enum class EEventType @@ -46,7 +47,7 @@ enum class EEventType EVENT_NOT_AVAILABLE }; -const std::array SelectModeString{"selectFirst", "selectPlayer", "selectRandom"}; +const std::array SelectModeString{"selectFirst", "selectPlayer", "selectRandom", "selectAll"}; const std::array VisitModeString{"unlimited", "once", "hero", "bonus", "limiter", "player"}; struct DLL_LINKAGE ResetInfo From 9d705a3961d65f69cddeac241a70dd9657b8e90a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Tue, 31 Oct 2023 18:38:08 +0100 Subject: [PATCH 002/250] Update docs --- docs/modders/Map_Objects/Rewardable.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/modders/Map_Objects/Rewardable.md b/docs/modders/Map_Objects/Rewardable.md index 245719060..4a62ebf1b 100644 --- a/docs/modders/Map_Objects/Rewardable.md +++ b/docs/modders/Map_Objects/Rewardable.md @@ -177,6 +177,7 @@ Rewardable object is defined similarly to other objects, with key difference bei // "selectFirst", - first reward which passes "limiter" will be granted to player // "selectPlayer", - player will be allowed to choose between rewards (e.g. treasure chest) // "selectRandom", - granted reward will be picked randomly when hero visits object +// "selectAll" - every reward which passes "limiter" will be granted to player "selectMode" : "selectFirst" } From 440b468e273f337da3740c33b81d671412a5fc64 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Thu, 18 Jan 2024 20:42:08 +0100 Subject: [PATCH 003/250] 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 004/250] 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 005/250] 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 006/250] 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 007/250] 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 008/250] 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 009/250] 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 010/250] 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 011/250] 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 012/250] 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 013/250] 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 014/250] 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 015/250] 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 016/250] 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 017/250] 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 018/250] 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 019/250] 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 020/250] 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 021/250] 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 022/250] 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 023/250] 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 024/250] 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 025/250] 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 026/250] 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 027/250] 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 028/250] 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 029/250] 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 030/250] 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 031/250] 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 032/250] 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 033/250] 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 034/250] 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 035/250] 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 036/250] 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 037/250] 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 038/250] 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 039/250] 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 040/250] 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 041/250] 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 042/250] 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 043/250] 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 044/250] 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 c5c46a7c9a8585671d4b1d38e3f644c19a5ed033 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 29 Jan 2024 22:05:11 +0200 Subject: [PATCH 045/250] 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 c9ebf32efd5704d2add6f895bc80269b8153ab32 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Feb 2024 00:12:30 +0200 Subject: [PATCH 046/250] 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 047/250] 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 048/250] 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 049/250] 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 03fcfe3392703d9fef52b4dc0f6b3f4f7972b027 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Feb 2024 02:36:57 +0200 Subject: [PATCH 050/250] 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 f97ffd8e9a572cef2cd4d50b8e153488bc298b93 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 2 Feb 2024 15:32:06 +0200 Subject: [PATCH 051/250] 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 6eef197ceadde502c0e5c7be31703d2c985cd251 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 3 Feb 2024 17:04:14 +0200 Subject: [PATCH 052/250] 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 b7df6064cdd183d895c69f1ab6fbd1011c98b80b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 3 Feb 2024 16:23:56 +0100 Subject: [PATCH 053/250] Add random map description and display it in RMG menu --- Mods/vcmi/config/vcmi/rmg/hdmod/blockbuster.JSON | 4 +++- Mods/vcmi/config/vcmi/rmg/hdmod/extreme.JSON | 4 ++-- Mods/vcmi/config/vcmi/rmg/hdmod/extreme2.JSON | 4 ++-- Mods/vcmi/config/vcmi/rmg/hdmod/marathon.JSON | 2 ++ Mods/vcmi/config/vcmi/rmg/hdmod/panic.JSON | 1 + Mods/vcmi/config/vcmi/rmg/hdmod/poorJebus.JSON | 2 +- Mods/vcmi/config/vcmi/rmg/hdmod/reckless.JSON | 2 +- Mods/vcmi/config/vcmi/rmg/hdmod/roadrunner.JSON | 2 +- Mods/vcmi/config/vcmi/rmg/hdmod/skirmish.JSON | 2 ++ Mods/vcmi/config/vcmi/rmg/hdmod/superslam.JSON | 2 +- Mods/vcmi/config/vcmi/rmg/hdmod/triad.JSON | 3 ++- Mods/vcmi/config/vcmi/rmg/hdmod/vortex.JSON | 2 +- client/lobby/RandomMapTab.cpp | 12 ++++++++++++ config/schemas/template.json | 4 ++++ docs/modders/Random_Map_Template.md | 2 ++ lib/rmg/CRmgTemplate.cpp | 6 ++++++ lib/rmg/CRmgTemplate.h | 2 ++ 17 files changed, 45 insertions(+), 11 deletions(-) diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/blockbuster.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/blockbuster.JSON index 0cccafbb0..971a9a9e2 100755 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/blockbuster.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/blockbuster.JSON @@ -1,9 +1,9 @@ { "Blockbuster M" : - //(ban fly/DD, 2 player, 15-Jun-03, midnight design) { "minSize" : "m", "maxSize" : "m", "players" : "2", + "description" : "Ban fly/DD, 2 player, 15-Jun-03, Midnight design", "zones" : { "1" : @@ -119,6 +119,7 @@ { "minSize" : "l", "maxSize" : "l", "players" : "2", + "description" : "Ban fly/DD, 2 player, 15-Jun-03, Midnight design", "zones" : { "1" : @@ -246,6 +247,7 @@ { "minSize" : "xl", "maxSize" : "xl", "players" : "2", + "description" : "Ban fly/DD, 2 player, 15-Jun-03, Midnight design", "zones" : { "1" : diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/extreme.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/extreme.JSON index bac5bc7bc..b1aabf8cd 100755 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/extreme.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/extreme.JSON @@ -1,9 +1,9 @@ { "Extreme L" : - //(ban fly/DD/orb inhibition/Castle-Necro-Conflux towns, 2 player, 3-Aug-03, midnight design) { "minSize" : "l", "maxSize" : "l", "players" : "2", + "description" : "Ban fly/DD/Orb of Inhibition/Castle-Necro-Conflux towns, 2 player, 3-Aug-03, Midnight design", "zones" : { "1" : @@ -180,10 +180,10 @@ ] }, "Extreme XL" : - //(ban fly/DD/orb inhibition/Castle-Necro-Conflux towns, 2 player, 3-Aug-03, midnight design) { "minSize" : "xl", "maxSize" : "xh", "players" : "2", + "description" : "Ban fly/DD/Orb of Inhibition/Castle-Necro-Conflux towns, 2 player, 3-Aug-03, Midnight design", "zones" : { "1" : diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/extreme2.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/extreme2.JSON index 62006c85e..eeaff98cd 100755 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/extreme2.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/extreme2.JSON @@ -1,9 +1,9 @@ { "Extreme II L": - //(ban fly/DD/orb inhibition/Castle-Necro-Conflux towns, 2 player, 3-Aug-03, midnight design)" { "minSize" : "l", "maxSize" : "l", "players" : "2", + "description": "Ban fly/DD/orb inhibition/Castle-Necro-Conflux towns, 2 player, 3-Aug-03, Midnight design", "zones" : { "1" : @@ -154,10 +154,10 @@ ] }, "Extreme II XL": - //(ban fly/DD/orb inhibition/Castle-Necro-Conflux towns, 2 player, 3-Aug-03, midnight design) { "minSize" : "xl", "maxSize" : "xh", "players" : "2", + "description": "Ban fly/DD/orb inhibition/Castle-Necro-Conflux towns, 2 player, 3-Aug-03, Midnight design", "zones" : { "1" : diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/marathon.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/marathon.JSON index cf9e8e970..5f44d593e 100755 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/marathon.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/marathon.JSON @@ -3,6 +3,7 @@ { "minSize" : "xl", "maxSize" : "xl", "players" : "2", + "description" : "Ban Fly/DD, 2 player, 31-May-03, Midnight design", "zones" : { "1" : @@ -322,6 +323,7 @@ { "minSize" : "xl+u", "maxSize" : "g+u", "players" : "2", + "description" : "Ban Fly/DD, 2 player, 31-May-03, Midnight design", "zones" : { "1" : diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/panic.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/panic.JSON index 773b68625..72e6369ce 100755 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/panic.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/panic.JSON @@ -3,6 +3,7 @@ { "minSize" : "m+u", "maxSize" : "l+u", "players" : "2", + "description" : "Ban Fly/DD, 2 player, 3-Aug-03, Midnight design", "zones" : { "1" : diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/poorJebus.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/poorJebus.JSON index 3113a91dd..ef7610aa6 100755 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/poorJebus.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/poorJebus.JSON @@ -1,9 +1,9 @@ { "Poor Jebus" : - //(made by Bjorn190, modified by Maretti and Angelito) { "minSize" : "m", "maxSize" : "xl+u", "players" : "2-4", + "description" : "Made by Bjorn190, modified by Maretti and Angelito", "zones" : { "1" : diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/reckless.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/reckless.JSON index f41c68cd3..93bf54143 100755 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/reckless.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/reckless.JSON @@ -1,9 +1,9 @@ { "Reckless" : - //(2 player, 6-Jan-03, midnight design) { "minSize" : "l", "maxSize" : "xl+u", "players" : "2", + "description" : "2 players, 6-Jan-03, Midnight design", "zones" : { "1" : diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/roadrunner.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/roadrunner.JSON index 063ec0b1f..e4afc15f5 100755 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/roadrunner.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/roadrunner.JSON @@ -1,9 +1,9 @@ { "Roadrunner" : - //(ban fly/DD, 2 player, 31-May-03, midnight design) { "minSize" : "xl", "maxSize" : "xl", "players" : "2", + "description" : "Ban fly/DD, 2 players, 31-May-03, Midnight design", "zones" : { "1" : diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/skirmish.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/skirmish.JSON index 99080a813..97fb6be6d 100755 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/skirmish.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/skirmish.JSON @@ -3,6 +3,7 @@ { "minSize" : "m", "maxSize" : "m", "players" : "2", + "description" : "Ban fly/DD, 2 players, 3-Aug-03, Midnight design", "zones" : { "1" : @@ -119,6 +120,7 @@ { "minSize" : "m+u", "maxSize" : "l", "players" : "2", + "description" : "Ban fly/DD, 2 players, 3-Aug-03, Midnight design", "zones" : { "1" : diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/superslam.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/superslam.JSON index 0bad27b1e..c24add0e1 100755 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/superslam.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/superslam.JSON @@ -1,9 +1,9 @@ { "SuperSlam" : - //(2 player, Large or XL no under) For powermonger players, meet by SLAMMING thru super zones. Your chances are not over until the fat lady sings! Should ban spec log along with normal random rules { "minSize" : "l", "maxSize" : "xl", "players" : "2", + "description" : "2 players, Large or XL, no under) For powermonger players, meet by SLAMMING thru super zones. Your chances are not over until the fat lady sings! Should ban spec log along with normal random rules", "zones" : { "1" : diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/triad.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/triad.JSON index e5def0045..5ede0dbc0 100644 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/triad.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/triad.JSON @@ -1,9 +1,9 @@ { "Triad L" : - //(ban fly/DD, 3 players, 9-Jan-03, midnight design) { "minSize" : "l", "maxSize" : "l", "players" : "2-3", + "description" : "Ban fly/DD, 3 players, 9-Jan-03, Midnight design", "zones" : { "1" : @@ -182,6 +182,7 @@ { "minSize" : "xl", "maxSize" : "xl", "players" : "3", + "description" : "Ban fly/DD, 3 players, 9-Jan-03, Midnight design", "zones" : { "1" : diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/vortex.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/vortex.JSON index 37a1940ae..0be7894dd 100644 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/vortex.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/vortex.JSON @@ -1,9 +1,9 @@ { "Vortex" : - //(ban fly/DD, 3-4 player, 31-May-03, midnight design) { "minSize" : "xl", "maxSize" : "xl", "players" : "2-4", + "description" : "Ban fly/DD, 3-4 players, 31-May-03, Midnight design", "zones" : { "1" : diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 8696ba1fa..c0a49f1b0 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -177,6 +177,18 @@ void RandomMapTab::updateMapInfoByHost() mapInfo->mapHeader->version = EMapFormat::VCMI; mapInfo->mapHeader->name.appendLocalString(EMetaText::GENERAL_TXT, 740); mapInfo->mapHeader->description.appendLocalString(EMetaText::GENERAL_TXT, 741); + + const auto * temp = mapGenOptions->getMapTemplate(); + if (temp) + { + auto randomTemplateDescription = temp->getDescription(); + if (!randomTemplateDescription.empty()) + { + auto description = std::string("\n\n") + randomTemplateDescription; + mapInfo->mapHeader->description.appendRawString(description); + } + } + mapInfo->mapHeader->difficulty = 1; // Normal mapInfo->mapHeader->height = mapGenOptions->getHeight(); mapInfo->mapHeader->width = mapGenOptions->getWidth(); diff --git a/config/schemas/template.json b/config/schemas/template.json index 67cbca49f..a1a353a6d 100644 --- a/config/schemas/template.json +++ b/config/schemas/template.json @@ -135,6 +135,10 @@ "description" : "Optional name - useful to have several template variations with same name", "type": "string" }, + "description" : { + "description" : "Optional info about template, author or special rules", + "type": "string" + }, "zones" : { "description" : "List of named zones", "type" : "object", diff --git a/docs/modders/Random_Map_Template.md b/docs/modders/Random_Map_Template.md index 0e31d9da7..1f7c00802 100644 --- a/docs/modders/Random_Map_Template.md +++ b/docs/modders/Random_Map_Template.md @@ -8,6 +8,8 @@ { //Optional name - useful to have several template variations with same name "name" : "Custom template name", + //Any info you want to be displayed in random map menu + "description" : "Detailed info and recommended rules", /// Minimal and maximal size of the map. Possible formats: /// Size code: s, m, l or xl for size with optional suffix "+u" for underground diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 384d186a0..75467694f 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -541,6 +541,11 @@ const std::string & CRmgTemplate::getName() const return name; } +const std::string & CRmgTemplate::getDescription() const +{ + return description; +} + const std::string & CRmgTemplate::getId() const { return id; @@ -682,6 +687,7 @@ int CRmgTemplate::CPlayerCountRange::minValue() const void CRmgTemplate::serializeJson(JsonSerializeFormat & handler) { handler.serializeString("name", name); + handler.serializeString("description", description); serializeSize(handler, minSize, "minSize"); serializeSize(handler, maxSize, "maxSize"); serializePlayers(handler, players, "players"); diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 2460babdd..1d0382ad1 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -248,6 +248,7 @@ public: void setName(const std::string & value); const std::string & getId() const; const std::string & getName() const; + const std::string & getDescription() const; const CPlayerCountRange & getPlayers() const; const CPlayerCountRange & getHumanPlayers() const; @@ -263,6 +264,7 @@ public: private: std::string id; std::string name; + std::string description; int3 minSize; int3 maxSize; CPlayerCountRange players; From 2c2bec791cb7e33d46f80f45fe9bb8bab619a2e4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 3 Feb 2024 19:08:45 +0200 Subject: [PATCH 054/250] 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 055/250] 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 056/250] 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 057/250] 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 058/250] 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 059/250] 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 060/250] 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 061/250] 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 062/250] 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 063/250] 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 064/250] 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 77f177adc7b84a7247160185ee15ef5ae171776c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 6 Feb 2024 16:28:35 +0200 Subject: [PATCH 065/250] 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 066/250] 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 23e1b38c4f69aef3dbdaa3d2dfb02072bf978248 Mon Sep 17 00:00:00 2001 From: Tytannial Date: Wed, 7 Feb 2024 08:50:40 +0800 Subject: [PATCH 067/250] 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 068/250] 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 069/250] 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 070/250] 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 071/250] 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 072/250] 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 a7c29e2a95cd0b6a42be8db78e55291b34f1bbbd Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Thu, 8 Feb 2024 19:07:15 +0100 Subject: [PATCH 073/250] CI: Only use json5 Python package for validation This simplifies the script and makes the output more readable --- .github/workflows/github.yml | 2 +- CI/linux-qt6/validate_json.py | 53 +++++++---------------------------- 2 files changed, 11 insertions(+), 44 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 7fa7adadd..c51cb8f7c 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -124,7 +124,7 @@ jobs: # also, running it on multiple presets is redundant and slightly increases already long CI built times if: ${{ startsWith(matrix.preset, 'linux-clang-test') }} run: | - pip3 install json5 jstyleson + pip3 install json5 python3 CI/linux-qt6/validate_json.py - name: Dependencies diff --git a/CI/linux-qt6/validate_json.py b/CI/linux-qt6/validate_json.py index 5fbe0ac76..886eb8e7d 100755 --- a/CI/linux-qt6/validate_json.py +++ b/CI/linux-qt6/validate_json.py @@ -6,55 +6,22 @@ from pathlib import Path from pprint import pprint import json5 -import jstyleson -import yaml -# 'json', 'json5' or 'yaml' -# json: strict, but doesn't preserve line numbers necessarily, since it strips comments before parsing -# json5: strict and preserves line numbers even for files with line comments -# yaml: less strict, allows e.g. leading zeros -VALIDATION_TYPE = "json5" +validation_failed = False -errors = [] -for path in sorted(Path(".").glob("**/*.json")): +for path in sorted(Path(".").glob("**/*.json"), key=lambda path: str(path).lower()): # because path is an object and not a string path_str = str(path) + if path_str.startswith("."): + continue + try: with open(path_str, "r") as file: - if VALIDATION_TYPE == "json": - jstyleson.load(file) - elif VALIDATION_TYPE == "json5": - json5.load(file) - elif VALIDATION_TYPE == "yaml": - file = file.read().replace("\t", " ") - file = file.replace("//", "#") - yaml.safe_load(file) - print(f"Validation of {path_str} succeeded") + json5.load(file) + print(f"✅ {path_str}") except Exception as exc: - print(f"Validation of {path_str} failed") - pprint(exc) + print(f"❌ {str(exc).replace('', path_str)}") + validation_failed = True - error_pos = path_str - - # create error position strings for each type of parser - if hasattr(exc, "pos"): - # 'json' - # https://stackoverflow.com/a/72850269/2278742 - error_pos = f"{path_str}:{exc.lineno}:{exc.colno}" - print(error_pos) - elif VALIDATION_TYPE == "json5": - # 'json5' - pos = re.findall(r"\d+", str(exc)) - error_pos = f"{path_str}:{pos[0]}:{pos[-1]}" - elif hasattr(exc, "problem_mark"): - # 'yaml' - mark = exc.problem_mark - error_pos = f"{path_str}:{mark.line+1}:{mark.column+1}" - print(error_pos) - - errors.append({"error_pos": error_pos, "error_msg": exc}) - -if errors: - print("The following JSON files are invalid:") - pprint(errors) +if validation_failed: sys.exit(1) From 4a0dd2da2c2e7d922c987e8844e14c334d9e4814 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 9 Feb 2024 23:02:41 +0200 Subject: [PATCH 074/250] 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 075/250] 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 076/250] 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 077/250] 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 078/250] 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 079/250] 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; From c2286e512631b8e154d826179dcfa6f20a17539b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 10 Feb 2024 19:57:23 +0200 Subject: [PATCH 080/250] Server now consists from library and separate executable projects --- CMakeLists.txt | 91 ++++++++++++++-------------- lib/CMakeLists.txt | 6 +- lib_server/CMakeLists.txt | 3 - server/CMakeLists.txt | 44 ++++---------- serverapp/CMakeLists.txt | 34 +++++++++++ {server => serverapp}/EntryPoint.cpp | 2 +- serverapp/StdInc.cpp | 2 + serverapp/StdInc.h | 14 +++++ 8 files changed, 111 insertions(+), 85 deletions(-) delete mode 100644 lib_server/CMakeLists.txt create mode 100644 serverapp/CMakeLists.txt rename {server => serverapp}/EntryPoint.cpp (99%) create mode 100644 serverapp/StdInc.cpp create mode 100644 serverapp/StdInc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index eb27ea4f6..489aa24fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,41 +41,50 @@ 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) - set(staticAI ON) - set(singleProcess ON) -endif() +# Platform-independent options option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF) option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF) -if(NOT ANDROID) - option(ENABLE_LAUNCHER "Enable compilation of launcher" ON) - option(ENABLE_EDITOR "Enable compilation of map editor" ON) -endif() option(ENABLE_TRANSLATIONS "Enable generation of translations for launcher and editor" ON) option(ENABLE_NULLKILLER_AI "Enable compilation of Nullkiller AI library" ON) +option(ENABLE_SERVER "Enable compilation of dedicated server" ON) +option(ENABLE_GITVERSION "Enable Version.cpp with Git commit hash" ON) + +# Compilation options + +option(ENABLE_PCH "Enable compilation using precompiled headers" ON) +option(ENABLE_DEBUG_CONSOLE "Enable debug console for Windows builds" ON) +option(ENABLE_STRICT_COMPILATION "Treat all compiler warnings as errors" OFF) +option(ENABLE_MULTI_PROCESS_BUILDS "Enable /MP flag for MSVS solution" ON) +option(ENABLE_COLORIZED_COMPILER_OUTPUT "Colorize compilation output (Clang/GNU)." ON) +option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" OFF) + +# Platform-specific options + +if(ANDROID) + set(ENABLE_STATIC_AI_LIBS ON) +else() + option(ENABLE_STATIC_AI_LIBS "Add AI code into VCMI lib directly" ON) + option(ENABLE_LAUNCHER "Enable compilation of launcher" ON) +endif() if(APPLE_IOS) set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix") set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen") - set(ENABLE_SINGLE_APP_BUILD ON) -else() - option(ENABLE_TEST "Enable compilation of unit tests" OFF) - option(ENABLE_SINGLE_APP_BUILD "Builds client and server as single executable" ${singleProcess}) endif() -option(ENABLE_PCH "Enable compilation using precompiled headers" ON) -option(ENABLE_GITVERSION "Enable Version.cpp with Git commit hash" ON) -option(ENABLE_DEBUG_CONSOLE "Enable debug console for Windows builds" ON) -option(ENABLE_STRICT_COMPILATION "Treat all compiler warnings as errors" OFF) -option(ENABLE_MULTI_PROCESS_BUILDS "Enable /MP flag for MSVS solution" ON) -option(COPY_CONFIG_ON_BUILD "Copies config folder into output directory at building phase" ON) -option(ENABLE_STATIC_AI_LIBS "Add AI code into VCMI lib directly" ${staticAI}) - -option(ENABLE_COLORIZED_COMPILER_OUTPUT "Colorize compilation output (Clang/GNU)." ON) +if(APPLE_IOS OR ANDROID) + option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF) # Used for Snap packages and also useful for debugging + option(ENABLE_LOBBY "Enable compilation of lobby server" OFF) + set(ENABLE_SINGLE_APP_BUILD ON) + set(ENABLE_EDITOR OFF) + set(COPY_CONFIG_ON_BUILD OFF) +else() + option(COPY_CONFIG_ON_BUILD "Copies config folder into output directory at building phase" ON) + option(ENABLE_EDITOR "Enable compilation of map editor" ON) + option(ENABLE_SINGLE_APP_BUILD "Builds client and launcher as single executable" OFF) + option(ENABLE_TEST "Enable compilation of unit tests" OFF) +endif() if(ENABLE_COLORIZED_COMPILER_OUTPUT) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") @@ -85,20 +94,6 @@ if(ENABLE_COLORIZED_COMPILER_OUTPUT) endif() endif() -# Used for Snap packages and also useful for debugging -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" ${buildLobby}) -endif() - -option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" OFF) if(ENABLE_CCACHE) find_program(CCACHE ccache REQUIRED) endif() @@ -133,11 +128,6 @@ if(ENABLE_ERM AND NOT ENABLE_LUA) set(ENABLE_LUA ON) endif() -# We don't want to deploy assets into build directory for android/iOS build -if((APPLE_IOS OR ANDROID) AND COPY_CONFIG_ON_BUILD) - set(COPY_CONFIG_ON_BUILD OFF) -endif() - ############################################ # Miscellaneous options # ############################################ @@ -614,16 +604,16 @@ add_subdirectory_with_folder("AI" AI) include(VCMI_lib) add_subdirectory(lib) -if(ENABLE_SINGLE_APP_BUILD) - add_subdirectory(lib_server) -endif() +add_subdirectory(server) if(ENABLE_ERM) add_subdirectory(scripting/erm) endif() + if(ENABLE_LUA) add_subdirectory(scripting/lua) endif() + if(NOT TARGET minizip::minizip) add_subdirectory_with_folder("3rdparty" lib/minizip) add_library(minizip::minizip ALIAS minizip) @@ -632,14 +622,21 @@ endif() if(ENABLE_LAUNCHER) add_subdirectory(launcher) endif() + if(ENABLE_EDITOR) add_subdirectory(mapeditor) endif() + if(ENABLE_LOBBY) add_subdirectory(lobby) endif() + add_subdirectory(client) -add_subdirectory(server) + +if(ENABLE_SERVER) + add_subdirectory(serverapp) +endif() + if(ENABLE_TEST) enable_testing() add_subdirectory(test) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b8b94e33e..fc2f75b3c 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -13,6 +13,6 @@ if(ENABLE_STATIC_AI_LIBS) else() add_main_lib(${VCMI_LIB_TARGET} SHARED) endif() -if(ENABLE_SINGLE_APP_BUILD) - target_compile_definitions(${VCMI_LIB_TARGET} PUBLIC VCMI_LIB_NAMESPACE=LIB_CLIENT) -endif() + +target_compile_definitions(${VCMI_LIB_TARGET} PUBLIC VCMI_LIB_NAMESPACE=VCMI) + diff --git a/lib_server/CMakeLists.txt b/lib_server/CMakeLists.txt deleted file mode 100644 index 193e4ac5c..000000000 --- a/lib_server/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_main_lib(vcmi_lib_server STATIC) -target_compile_definitions(vcmi_lib_server PUBLIC VCMI_LIB_NAMESPACE=LIB_SERVER) -target_compile_definitions(vcmi_lib_server PUBLIC VCMI_DLL_STATIC=1) diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index efc6a0432..44d363f7a 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -1,4 +1,4 @@ -set(server_SRCS +set(libserver_SRCS StdInc.cpp battles/BattleActionProcessor.cpp @@ -15,7 +15,6 @@ set(server_SRCS processors/PlayerMessageProcessor.cpp processors/TurnOrderProcessor.cpp - EntryPoint.cpp CGameHandler.cpp GlobalLobbyProcessor.cpp ServerSpellCastEnvironment.cpp @@ -25,7 +24,7 @@ set(server_SRCS TurnTimerHandler.cpp ) -set(server_HEADERS +set(libserver_HEADERS StdInc.h battles/BattleActionProcessor.h @@ -51,45 +50,28 @@ set(server_HEADERS TurnTimerHandler.h ) -assign_source_group(${server_SRCS} ${server_HEADERS}) +assign_source_group(${libserver_SRCS} ${libserver_HEADERS}) -if(ENABLE_SINGLE_APP_BUILD) - add_library(vcmiserver STATIC ${server_SRCS} ${server_HEADERS}) - target_compile_definitions(vcmiserver PUBLIC VCMI_DLL_STATIC=1) - set(server_LIBS vcmi_lib_server) -else() - if(ANDROID) - add_library(vcmiserver SHARED ${server_SRCS} ${server_HEADERS}) - else() - add_executable(vcmiserver ${server_SRCS} ${server_HEADERS}) - endif() - set(server_LIBS vcmi) -endif() +add_library(libserver STATIC ${libserver_SRCS} ${libserver_HEADERS}) +set(libserver_LIBS vcmi) if(CMAKE_SYSTEM_NAME MATCHES FreeBSD OR HAIKU) - set(server_LIBS execinfo ${server_LIBS}) + set(libserver_LIBS execinfo ${libserver_LIBS}) endif() -target_link_libraries(vcmiserver PRIVATE ${server_LIBS} minizip::minizip) -target_include_directories(vcmiserver +target_link_libraries(libserver PRIVATE ${libserver_LIBS} minizip::minizip) + +target_include_directories(libserver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) if(WIN32) set_target_properties(vcmiserver PROPERTIES - OUTPUT_NAME "VCMI_server" - PROJECT_LABEL "VCMI_server" + OUTPUT_NAME "VCMI_libserver" + PROJECT_LABEL "VCMI_libserver" ) endif() -vcmi_set_output_dir(vcmiserver "") -enable_pch(vcmiserver) - -if(NOT ENABLE_SINGLE_APP_BUILD) - if(ANDROID) - install(TARGETS vcmiserver DESTINATION ${LIB_DIR}) - else() - install(TARGETS vcmiserver DESTINATION ${BIN_DIR}) - endif() -endif() +vcmi_set_output_dir(libserver "") +enable_pch(libserver) diff --git a/serverapp/CMakeLists.txt b/serverapp/CMakeLists.txt new file mode 100644 index 000000000..f3716198a --- /dev/null +++ b/serverapp/CMakeLists.txt @@ -0,0 +1,34 @@ +set(appserver_SRCS + StdInc.cpp + EntryPoint.cpp +) + +set(appserver_HEADERS + StdInc.h +) + +assign_source_group(${appserver_SRCS} ${appserver_HEADERS}) +add_executable(vcmiserver ${appserver_SRCS} ${appserver_HEADERS}) +set(appserver_LIBS vcmi) + +if(CMAKE_SYSTEM_NAME MATCHES FreeBSD OR HAIKU) + set(appserver_LIBS execinfo ${appserver_LIBS}) +endif() +target_link_libraries(vcmiserver PRIVATE ${appserver_LIBS} minizip::minizip libserver) + +target_include_directories(vcmiserver + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} +) + +if(WIN32) + set_target_properties(vcmiserver + PROPERTIES + OUTPUT_NAME "VCMI_server" + PROJECT_LABEL "VCMI_server" + ) +endif() + +vcmi_set_output_dir(vcmiserver "") +enable_pch(vcmiserver) + +install(TARGETS vcmiserver DESTINATION ${BIN_DIR}) diff --git a/server/EntryPoint.cpp b/serverapp/EntryPoint.cpp similarity index 99% rename from server/EntryPoint.cpp rename to serverapp/EntryPoint.cpp index 145027801..306706189 100644 --- a/server/EntryPoint.cpp +++ b/serverapp/EntryPoint.cpp @@ -9,7 +9,7 @@ */ #include "StdInc.h" -#include "CVCMIServer.h" +#include "../server/CVCMIServer.h" #include "../lib/CConsoleHandler.h" #include "../lib/logging/CBasicLogConfigurator.h" diff --git a/serverapp/StdInc.cpp b/serverapp/StdInc.cpp new file mode 100644 index 000000000..dd7f66cb8 --- /dev/null +++ b/serverapp/StdInc.cpp @@ -0,0 +1,2 @@ +// Creates the precompiled header +#include "StdInc.h" diff --git a/serverapp/StdInc.h b/serverapp/StdInc.h new file mode 100644 index 000000000..d03216bdf --- /dev/null +++ b/serverapp/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 0d263c557111e9f69f53c912ab5a4b7bc3905ce6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 10 Feb 2024 23:56:02 +0200 Subject: [PATCH 081/250] Implemented option to run server as a thread with shared VLC --- CMakeLists.txt | 9 ++- client/CMakeLists.txt | 12 +-- client/CServerHandler.cpp | 144 ++++----------------------------- client/CServerHandler.h | 4 +- client/Client.cpp | 20 ----- client/ServerRunner.cpp | 96 ++++++++++++++++++++++ client/ServerRunner.h | 57 +++++++++++++ client/mainmenu/CMainMenu.cpp | 12 --- lib/CMakeLists.txt | 6 +- lib/VCMIDirs.cpp | 4 - server/CMakeLists.txt | 26 +++--- server/CVCMIServer.cpp | 23 +++--- server/CVCMIServer.h | 21 +---- server/NetPacksLobbyServer.cpp | 2 +- serverapp/CMakeLists.txt | 14 ++-- serverapp/EntryPoint.cpp | 77 ++---------------- 16 files changed, 229 insertions(+), 298 deletions(-) create mode 100644 client/ServerRunner.cpp create mode 100644 client/ServerRunner.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 489aa24fc..2b853427d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,8 +63,9 @@ option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" O if(ANDROID) set(ENABLE_STATIC_AI_LIBS ON) + set(ENABLE_LAUNCHER OFF) else() - option(ENABLE_STATIC_AI_LIBS "Add AI code into VCMI lib directly" ON) + option(ENABLE_STATIC_AI_LIBS "Build all libraries statically (NOT only AI)" OFF) option(ENABLE_LAUNCHER "Enable compilation of launcher" ON) endif() @@ -75,15 +76,17 @@ endif() if(APPLE_IOS OR ANDROID) option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF) # Used for Snap packages and also useful for debugging - option(ENABLE_LOBBY "Enable compilation of lobby server" OFF) set(ENABLE_SINGLE_APP_BUILD ON) set(ENABLE_EDITOR OFF) + set(ENABLE_TEST OFF) + set(ENABLE_LOBBY OFF) set(COPY_CONFIG_ON_BUILD OFF) else() option(COPY_CONFIG_ON_BUILD "Copies config folder into output directory at building phase" ON) option(ENABLE_EDITOR "Enable compilation of map editor" ON) option(ENABLE_SINGLE_APP_BUILD "Builds client and launcher as single executable" OFF) option(ENABLE_TEST "Enable compilation of unit tests" OFF) + option(ENABLE_LOBBY "Enable compilation of lobby server" OFF) endif() if(ENABLE_COLORIZED_COMPILER_OUTPUT) @@ -240,7 +243,7 @@ if(ENABLE_EDITOR) endif() if(ENABLE_SINGLE_APP_BUILD) - add_definitions(-DSINGLE_PROCESS_APP=1) + add_definitions(-DENABLE_SINGLE_APP_BUILD) endif() if(APPLE_IOS) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index bd5791476..195843198 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -164,6 +164,7 @@ set(client_SRCS HeroMovementController.cpp NetPacksClient.cpp NetPacksLobbyClient.cpp + ServerRunner.cpp ) set(client_HEADERS @@ -346,6 +347,7 @@ set(client_HEADERS ClientNetPackVisitors.h HeroMovementController.h LobbyClientNetPackVisitors.h + ServerRunner.h resource.h ) @@ -451,12 +453,12 @@ elseif(APPLE_IOS) set(CMAKE_EXE_LINKER_FLAGS "-Wl,-e,_client_main") endif() -if(ENABLE_SINGLE_APP_BUILD) - target_link_libraries(vcmiclient PRIVATE vcmiserver) - if(ENABLE_LAUNCHER) - target_link_libraries(vcmiclient PRIVATE vcmilauncher) - endif() +target_link_libraries(vcmiclient PRIVATE vcmiservercommon) + +if(ENABLE_SINGLE_APP_BUILD AND ENABLE_LAUNCHER) + target_link_libraries(vcmiclient PRIVATE vcmilauncher) endif() + target_link_libraries(vcmiclient PRIVATE ${VCMI_LIB_TARGET} SDL2::SDL2 SDL2::Image SDL2::Mixer SDL2::TTF ) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 2899906a9..fdb4f6292 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -12,6 +12,7 @@ #include "CServerHandler.h" #include "Client.h" #include "CGameInfo.h" +#include "ServerRunner.h" #include "CPlayerInterface.h" #include "gui/CGuiHandler.h" #include "gui/WindowHandler.h" @@ -25,17 +26,6 @@ #include "mainmenu/CPrologEpilogVideo.h" #include "mainmenu/CHighScoreScreen.h" -#ifdef VCMI_ANDROID -#include "../lib/CAndroidVMHelper.h" -#elif defined(VCMI_IOS) -#include "ios/utils.h" -#include -#endif - -#ifdef SINGLE_PROCESS_APP -#include "../server/CVCMIServer.h" -#endif - #include "../lib/CConfigHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CThreadHelper.h" @@ -61,16 +51,8 @@ #include -#ifdef VCMI_WINDOWS -#include -#endif - template class CApplyOnLobby; -#if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP) -extern std::atomic_bool androidTestServerReadyFlag; -#endif - class CBaseForLobbyApply { public: @@ -195,67 +177,17 @@ INetworkHandler & CServerHandler::getNetworkHandler() void CServerHandler::startLocalServerAndConnect(bool connectToLobby) { - if(threadRunLocalServer.joinable()) - threadRunLocalServer.join(); - - th->update(); - -#if defined(SINGLE_PROCESS_APP) - boost::condition_variable cond; - std::vector args{"--port=" + std::to_string(getLocalPort())}; - if(connectToLobby) - args.push_back("--lobby"); - - threadRunLocalServer = boost::thread([&cond, args] { - setThreadName("CVCMIServer"); - CVCMIServer::create(&cond, args); - }); -#elif defined(VCMI_ANDROID) - { - CAndroidVMHelper envHelper; - envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "startServer", true); - } +#ifdef VCMI_MOBILE + // mobile apps can't spawn separate processes - only thread mode is available + serverRunner.reset(new ServerThreadRunner()); #else - threadRunLocalServer = boost::thread(&CServerHandler::threadRunServer, this, connectToLobby); //runs server executable; -#endif - logNetwork->trace("Setting up thread calling server: %d ms", th->getDiff()); - - th->update(); - -#ifdef SINGLE_PROCESS_APP - { -#ifdef VCMI_IOS - dispatch_sync(dispatch_get_main_queue(), ^{ - iOS_utils::showLoadingIndicator(); - }); + if (settings["server"]["useProcess"].Bool()) + serverRunner.reset(new ServerProcessRunner()); + else + serverRunner.reset(new ServerThreadRunner()); #endif - boost::mutex m; - boost::unique_lock lock{m}; - logNetwork->info("waiting for server"); - cond.wait(lock); - logNetwork->info("server is ready"); - -#ifdef VCMI_IOS - dispatch_sync(dispatch_get_main_queue(), ^{ - iOS_utils::hideLoadingIndicator(); - }); -#endif - } -#elif defined(VCMI_ANDROID) - logNetwork->info("waiting for server"); - while(!androidTestServerReadyFlag.load()) - { - logNetwork->info("still waiting..."); - boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); - } - logNetwork->info("waiting for server finished..."); - androidTestServerReadyFlag = false; -#endif - logNetwork->trace("Waiting for server: %d ms", th->getDiff()); - - th->update(); //put breakpoint here to attach to server before it does something stupid - + serverRunner->start(getLocalPort(), connectToLobby); connectToServer(getLocalHostname(), getLocalPort()); logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff()); @@ -374,7 +306,7 @@ void CServerHandler::setState(EClientState newState) bool CServerHandler::isServerLocal() const { - return threadRunLocalServer.joinable(); + return serverRunner != nullptr; } bool CServerHandler::isHost() const @@ -758,7 +690,6 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared } }; - threadRunLocalServer.join(); if(epilogue.hasPrologEpilog) { GH.windows().createAndPushWindow(epilogue, finisher); @@ -899,6 +830,12 @@ void CServerHandler::onPacketReceived(const std::shared_ptr void CServerHandler::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { + if (serverRunner) + { + serverRunner->wait(); + serverRunner.reset(); + } + if(getState() == EClientState::DISCONNECTING) { assert(networkConnection == nullptr); @@ -942,55 +879,6 @@ void CServerHandler::visitForClient(CPackForClient & clientPack) client->handlePack(&clientPack); } -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"; - if(connectToLobby) - comm += " --lobby"; - - comm += " > \"" + logName + '\"'; - logGlobal->info("Server command line: %s", comm); - -#ifdef VCMI_WINDOWS - int result = -1; - const auto bufSize = ::MultiByteToWideChar(CP_UTF8, 0, comm.c_str(), comm.size(), nullptr, 0); - if(bufSize > 0) - { - std::wstring wComm(bufSize, {}); - const auto convertResult = ::MultiByteToWideChar(CP_UTF8, 0, comm.c_str(), comm.size(), &wComm[0], bufSize); - if(convertResult > 0) - result = ::_wsystem(wComm.c_str()); - else - logNetwork->error("Error " + std::to_string(GetLastError()) + ": failed to convert server launch command to wide string: " + comm); - } - else - logNetwork->error("Error " + std::to_string(GetLastError()) + ": failed to obtain buffer length to convert server launch command to wide string : " + comm); -#else - int result = std::system(comm.c_str()); -#endif - if (result == 0) - { - logNetwork->info("Server closed correctly"); - } - else - { - boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); - - 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); - } -#endif -} void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const { diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 59573676d..7b883de22 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -36,6 +36,7 @@ VCMI_LIB_NAMESPACE_END class CClient; class CBaseForLobbyApply; class GlobalLobbyClient; +class IServerRunner; class HighScoreCalculation; class HighScoreParameter; @@ -100,17 +101,16 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor std::shared_ptr networkConnection; std::unique_ptr lobbyClient; std::unique_ptr> applier; + std::unique_ptr serverRunner; std::shared_ptr mapToStart; std::vector myNames; std::shared_ptr highScoreCalc; - boost::thread threadRunLocalServer; boost::thread threadNetwork; std::atomic state; void threadRunNetwork(); - void threadRunServer(bool connectToLobby); void sendLobbyPack(const CPackForLobby & pack) const override; diff --git a/client/Client.cpp b/client/Client.cpp index 9ff148e9e..00cbcbe9a 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -46,10 +46,6 @@ #ifdef VCMI_ANDROID #include "lib/CAndroidVMHelper.h" - -#ifndef SINGLE_PROCESS_APP -std::atomic_bool androidTestServerReadyFlag; -#endif #endif ThreadSafeVector CClient::waitingRequest; @@ -718,22 +714,6 @@ void CClient::removeGUI() const } #ifdef VCMI_ANDROID -#ifndef SINGLE_PROCESS_APP -extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jclass cls) -{ - logNetwork->info("Received server closed signal"); - if (CSH) { - CSH->campaignServerRestartLock.setn(false); - } -} - -extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerReady(JNIEnv * env, jclass cls) -{ - logNetwork->info("Received server ready signal"); - androidTestServerReadyFlag.store(true); -} -#endif - extern "C" JNIEXPORT jboolean JNICALL Java_eu_vcmi_vcmi_NativeMethods_tryToSaveTheGame(JNIEnv * env, jclass cls) { logGlobal->info("Received emergency save game request"); diff --git a/client/ServerRunner.cpp b/client/ServerRunner.cpp new file mode 100644 index 000000000..891c0b134 --- /dev/null +++ b/client/ServerRunner.cpp @@ -0,0 +1,96 @@ +/* + * ServerRunner.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 "ServerRunner.h" + +#include "../lib/VCMIDirs.h" +#include "../lib/CThreadHelper.h" +#include "../server/CVCMIServer.h" + +#ifndef VCMI_MOBILE +#include +#include +#endif + +ServerThreadRunner::ServerThreadRunner() = default; +ServerThreadRunner::~ServerThreadRunner() = default; +ServerProcessRunner::ServerProcessRunner() = default; +ServerProcessRunner::~ServerProcessRunner() = default; + +void ServerThreadRunner::start(uint16_t port, bool connectToLobby) +{ + setThreadName("runServer"); + + server = std::make_unique(port, connectToLobby, true); + + threadRunLocalServer = boost::thread([this]{ + server->run(); + }); +} + +void ServerThreadRunner::stop() +{ + server->setState(EServerState::SHUTDOWN); +} + +int ServerThreadRunner::wait() +{ + threadRunLocalServer.join(); + return 0; +} + +void ServerProcessRunner::stop() +{ + child->terminate(); +} + +int ServerProcessRunner::wait() +{ + child->wait(); + + return child->exit_code(); + +// if (child->exit_code() == 0) +// { +// logNetwork->info("Server closed correctly"); +// } +// else +// { +// boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); +// +// 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); +// } +} + +void ServerProcessRunner::start(uint16_t port, bool connectToLobby) +{ + boost::filesystem::path serverPath = VCMIDirs::get().serverPath(); + boost::filesystem::path logPath = VCMIDirs::get().userLogsPath() / "server_log.txt"; + std::vector args; + args.push_back("--port=" + std::to_string(port)); + args.push_back("--run-by-client"); + if(connectToLobby) + args.push_back("--lobby"); + + std::error_code ec; + child = std::make_unique(serverPath, args, ec, boost::process::std_out > logPath); + + if (ec) + throw std::runtime_error("Failed to start server! Reason: " + ec.message()); +} + + diff --git a/client/ServerRunner.h b/client/ServerRunner.h new file mode 100644 index 000000000..19b73191e --- /dev/null +++ b/client/ServerRunner.h @@ -0,0 +1,57 @@ +/* + * ServerRunner.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 CVCMIServer; + +class IServerRunner +{ +public: + virtual void start(uint16_t port, bool connectToLobby) = 0; + virtual void stop() = 0; + virtual int wait() = 0; + + virtual ~IServerRunner() = default; +}; + +/// Server instance will run as a thread of client process +class ServerThreadRunner : public IServerRunner, boost::noncopyable +{ + std::unique_ptr server; + boost::thread threadRunLocalServer; +public: + void start(uint16_t port, bool connectToLobby) override; + void stop() override; + int wait() override; + + ServerThreadRunner(); + ~ServerThreadRunner(); +}; + +#ifndef VCMI_MOBILE + +namespace boost::process { +class child; +} + +/// Server instance will run as a separate process +class ServerProcessRunner : public IServerRunner, boost::noncopyable +{ + std::unique_ptr child; + +public: + void start(uint16_t port, bool connectToLobby) override; + void stop() override; + int wait() override; + + ServerProcessRunner(); + ~ServerProcessRunner(); +}; +#endif diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 4ff244b9b..e4d8fd207 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -60,11 +60,6 @@ #include "../../lib/CRandomGenerator.h" #include "../../lib/CondSh.h" -#if defined(SINGLE_PROCESS_APP) && defined(VCMI_ANDROID) -#include "../../server/CVCMIServer.h" -#include -#endif - std::shared_ptr CMM; ISelectionScreenInfo * SEL; @@ -599,13 +594,6 @@ void CSimpleJoinScreen::onChange(const std::string & newText) 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 - // as server runs in a separate thread, it must not attempt to search for Java classes (and they're already cached anyway) - // https://github.com/libsdl-org/SDL/blob/main/docs/README-android.md#threads-and-the-java-vm - CVCMIServer::reuseClientJNIEnv(SDL_AndroidGetJNIEnv()); -#endif - if(addr.empty()) CSH->startLocalServerAndConnect(false); else diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index fc2f75b3c..2e66e204b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -14,5 +14,9 @@ else() add_main_lib(${VCMI_LIB_TARGET} SHARED) endif() -target_compile_definitions(${VCMI_LIB_TARGET} PUBLIC VCMI_LIB_NAMESPACE=VCMI) +# no longer necessary, but might be useful to keep in future +# unfortunately at the moment tests do not support namespaced build, so enable only on some systems +if(APPLE_IOS OR ANDROID) + target_compile_definitions(${VCMI_LIB_TARGET} PUBLIC VCMI_LIB_NAMESPACE=VCMI) +endif() diff --git a/lib/VCMIDirs.cpp b/lib/VCMIDirs.cpp index 3c0692885..83bf7ee68 100644 --- a/lib/VCMIDirs.cpp +++ b/lib/VCMIDirs.cpp @@ -368,11 +368,7 @@ bool IVCMIDirsUNIX::developmentMode() const { // We want to be able to run VCMI from single directory. E.g to run from build output directory const bool result = bfs::exists("AI") && bfs::exists("config") && bfs::exists("Mods") && bfs::exists("vcmiclient"); -#if SINGLE_PROCESS_APP return result; -#else - return result && bfs::exists("vcmiserver"); -#endif } bfs::path IVCMIDirsUNIX::clientPath() const { return binaryPath() / "vcmiclient"; } diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 44d363f7a..6f7093ea4 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -1,4 +1,4 @@ -set(libserver_SRCS +set(vcmiservercommon_SRCS StdInc.cpp battles/BattleActionProcessor.cpp @@ -24,7 +24,7 @@ set(libserver_SRCS TurnTimerHandler.cpp ) -set(libserver_HEADERS +set(vcmiservercommon_HEADERS StdInc.h battles/BattleActionProcessor.h @@ -50,28 +50,28 @@ set(libserver_HEADERS TurnTimerHandler.h ) -assign_source_group(${libserver_SRCS} ${libserver_HEADERS}) +assign_source_group(${vcmiservercommon_SRCS} ${vcmiservercommon_HEADERS}) -add_library(libserver STATIC ${libserver_SRCS} ${libserver_HEADERS}) -set(libserver_LIBS vcmi) +add_library(vcmiservercommon STATIC ${vcmiservercommon_SRCS} ${vcmiservercommon_HEADERS}) +set(vcmiservercommon_LIBS vcmi) if(CMAKE_SYSTEM_NAME MATCHES FreeBSD OR HAIKU) - set(libserver_LIBS execinfo ${libserver_LIBS}) + set(vcmiservercommon_LIBS execinfo ${vcmiservercommon_LIBS}) endif() -target_link_libraries(libserver PRIVATE ${libserver_LIBS} minizip::minizip) +target_link_libraries(vcmiservercommon PRIVATE ${vcmiservercommon_LIBS} minizip::minizip) -target_include_directories(libserver +target_include_directories(vcmiservercommon PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) if(WIN32) - set_target_properties(vcmiserver + set_target_properties(vcmiservercommon PROPERTIES - OUTPUT_NAME "VCMI_libserver" - PROJECT_LABEL "VCMI_libserver" + OUTPUT_NAME "VCMI_vcmiservercommon" + PROJECT_LABEL "VCMI_vcmiservercommon" ) endif() -vcmi_set_output_dir(libserver "") -enable_pch(libserver) +vcmi_set_output_dir(vcmiservercommon "") +enable_pch(vcmiservercommon) diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 1ec3d3299..3af26c254 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -116,10 +116,11 @@ public: } }; -CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts) +CVCMIServer::CVCMIServer(uint16_t port, bool connectToLobby, bool runByClient) : currentClientId(1) , currentPlayerId(1) - , cmdLineOptions(opts) + , port(port) + , runByClient(runByClient) { uuid = boost::uuids::to_string(boost::uuids::random_generator()()); logNetwork->trace("CVCMIServer created! UUID: %s", uuid); @@ -128,7 +129,7 @@ CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts) networkHandler = INetworkHandler::createHandler(); - if(cmdLineOptions.count("lobby")) + if(connectToLobby) lobbyProcessor = std::make_unique(*this); else startAcceptingIncomingConnections(); @@ -138,10 +139,6 @@ 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); networkServer = networkHandler->createServerTCP(*this); @@ -197,15 +194,13 @@ std::shared_ptr CVCMIServer::findConnection(const std::shared_ptrrun(); } diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 63da18f76..bc19a097b 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -12,12 +12,6 @@ #include "../lib/network/NetworkInterface.h" #include "../lib/StartInfo.h" -#include - -#if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP) -#define VCMI_ANDROID_DUAL_PROCESS 1 -#endif - VCMI_LIB_NAMESPACE_BEGIN class CMapInfo; @@ -64,6 +58,8 @@ class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INet int currentClientId; ui8 currentPlayerId; + uint16_t port; + bool runByClient; public: /// List of all active connections @@ -76,13 +72,13 @@ public: void onTimer() override; std::shared_ptr gh; - boost::program_options::variables_map cmdLineOptions; - CVCMIServer(boost::program_options::variables_map & opts); + CVCMIServer(uint16_t port, bool connectToLobby, bool runByClient); ~CVCMIServer(); void run(); + bool wasStartedByClient() const; bool prepareToStartGame(); void prepareToRestart(); void startGameImmediately(); @@ -131,13 +127,4 @@ public: void setCampaignBonus(int bonusId); ui8 getIdOfFirstUnallocatedPlayer() const; - -#if VCMI_ANDROID_DUAL_PROCESS - static void create(); -#elif defined(SINGLE_PROCESS_APP) - static void create(boost::condition_variable * cond, const std::vector & args); -# ifdef VCMI_ANDROID - static void reuseClientJNIEnv(void * jniEnv); -# endif // VCMI_ANDROID -#endif // VCMI_ANDROID_DUAL_PROCESS }; diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index d312befdd..3695add5c 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -81,7 +81,7 @@ void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientDisconnected(LobbyC if(pack.shutdownServer) { - if(!srv.cmdLineOptions.count("run-by-client")) + if(!srv.wasStartedByClient()) { result = false; return; diff --git a/serverapp/CMakeLists.txt b/serverapp/CMakeLists.txt index f3716198a..059e6132d 100644 --- a/serverapp/CMakeLists.txt +++ b/serverapp/CMakeLists.txt @@ -1,20 +1,20 @@ -set(appserver_SRCS +set(serverapp_SRCS StdInc.cpp EntryPoint.cpp ) -set(appserver_HEADERS +set(serverapp_HEADERS StdInc.h ) -assign_source_group(${appserver_SRCS} ${appserver_HEADERS}) -add_executable(vcmiserver ${appserver_SRCS} ${appserver_HEADERS}) -set(appserver_LIBS vcmi) +assign_source_group(${serverapp_SRCS} ${serverapp_HEADERS}) +add_executable(vcmiserver ${serverapp_SRCS} ${serverapp_HEADERS}) +set(serverapp_LIBS vcmi) if(CMAKE_SYSTEM_NAME MATCHES FreeBSD OR HAIKU) - set(appserver_LIBS execinfo ${appserver_LIBS}) + set(serverapp_LIBS execinfo ${serverapp_LIBS}) endif() -target_link_libraries(vcmiserver PRIVATE ${appserver_LIBS} minizip::minizip libserver) +target_link_libraries(vcmiserver PRIVATE ${serverapp_LIBS} minizip::minizip vcmiservercommon) target_include_directories(vcmiserver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/serverapp/EntryPoint.cpp b/serverapp/EntryPoint.cpp index 306706189..d1b937f87 100644 --- a/serverapp/EntryPoint.cpp +++ b/serverapp/EntryPoint.cpp @@ -16,12 +16,6 @@ #include "../lib/VCMIDirs.h" #include "../lib/VCMI_Lib.h" -#ifdef VCMI_ANDROID -#include -#include -#include "lib/CAndroidVMHelper.h" -#endif - #include const std::string SERVER_NAME_AFFIX = "server"; @@ -50,13 +44,8 @@ static void handleCommandOptions(int argc, const char * argv[], boost::program_o } } -#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); @@ -75,31 +64,14 @@ static void handleCommandOptions(int argc, const char * argv[], boost::program_o 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); @@ -113,58 +85,21 @@ int main(int argc, const char * argv[]) std::srand(static_cast(time(nullptr))); { - CVCMIServer server(opts); + bool connectToLobby = opts.count("lobby"); + bool runByClient = opts.count("runByClient"); + uint16_t port = 3030; + if(opts.count("port")) + port = opts["port"].as(); -#ifdef SINGLE_PROCESS_APP - boost::condition_variable * cond = reinterpret_cast(const_cast(argv[0])); - cond->notify_one(); -#endif + CVCMIServer server(port, connectToLobby, runByClient); 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 From f08c9f4d59ef41a0592c1759683fd874b733fce8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 11 Feb 2024 00:00:58 +0200 Subject: [PATCH 082/250] Renamed ENABLE_STATIC_AI_LIBS option to match its actual effect --- AI/BattleAI/CMakeLists.txt | 4 ++-- AI/EmptyAI/CMakeLists.txt | 4 ++-- AI/Nullkiller/CMakeLists.txt | 4 ++-- AI/StupidAI/CMakeLists.txt | 4 ++-- AI/VCAI/CMakeLists.txt | 4 ++-- CMakeLists.txt | 35 ++++++++++++++++++----------------- client/CMakeLists.txt | 2 +- lib/CMakeLists.txt | 2 +- 8 files changed, 30 insertions(+), 29 deletions(-) diff --git a/AI/BattleAI/CMakeLists.txt b/AI/BattleAI/CMakeLists.txt index 335c92f5c..1ce31e7da 100644 --- a/AI/BattleAI/CMakeLists.txt +++ b/AI/BattleAI/CMakeLists.txt @@ -26,12 +26,12 @@ set(battleAI_HEADERS BattleExchangeVariant.h ) -if(NOT ENABLE_STATIC_AI_LIBS) +if(NOT ENABLE_STATIC_LIBS) list(APPEND battleAI_SRCS main.cpp StdInc.cpp) endif() assign_source_group(${battleAI_SRCS} ${battleAI_HEADERS}) -if(ENABLE_STATIC_AI_LIBS) +if(ENABLE_STATIC_LIBS) add_library(BattleAI STATIC ${battleAI_SRCS} ${battleAI_HEADERS}) else() add_library(BattleAI SHARED ${battleAI_SRCS} ${battleAI_HEADERS}) diff --git a/AI/EmptyAI/CMakeLists.txt b/AI/EmptyAI/CMakeLists.txt index cd594b0f3..a463f173b 100644 --- a/AI/EmptyAI/CMakeLists.txt +++ b/AI/EmptyAI/CMakeLists.txt @@ -8,12 +8,12 @@ set(emptyAI_HEADERS CEmptyAI.h ) -if(NOT ENABLE_STATIC_AI_LIBS) +if(NOT ENABLE_STATIC_LIBS) list(APPEND emptyAI_SRCS main.cpp StdInc.cpp) endif() assign_source_group(${emptyAI_SRCS} ${emptyAI_HEADERS}) -if(ENABLE_STATIC_AI_LIBS) +if(ENABLE_STATIC_LIBS) add_library(EmptyAI STATIC ${emptyAI_SRCS} ${emptyAI_HEADERS}) else() add_library(EmptyAI SHARED ${emptyAI_SRCS} ${emptyAI_HEADERS}) diff --git a/AI/Nullkiller/CMakeLists.txt b/AI/Nullkiller/CMakeLists.txt index 042cb5a0d..b775630f3 100644 --- a/AI/Nullkiller/CMakeLists.txt +++ b/AI/Nullkiller/CMakeLists.txt @@ -125,12 +125,12 @@ set(Nullkiller_HEADERS AIGateway.h ) -if(NOT ENABLE_STATIC_AI_LIBS) +if(NOT ENABLE_STATIC_LIBS) list(APPEND Nullkiller_SRCS main.cpp StdInc.cpp) endif() assign_source_group(${Nullkiller_SRCS} ${Nullkiller_HEADERS}) -if(ENABLE_STATIC_AI_LIBS) +if(ENABLE_STATIC_LIBS) add_library(Nullkiller STATIC ${Nullkiller_SRCS} ${Nullkiller_HEADERS}) else() add_library(Nullkiller SHARED ${Nullkiller_SRCS} ${Nullkiller_HEADERS}) diff --git a/AI/StupidAI/CMakeLists.txt b/AI/StupidAI/CMakeLists.txt index e5cab7296..5177a2561 100644 --- a/AI/StupidAI/CMakeLists.txt +++ b/AI/StupidAI/CMakeLists.txt @@ -8,12 +8,12 @@ set(stupidAI_HEADERS StupidAI.h ) -if(NOT ENABLE_STATIC_AI_LIBS) +if(NOT ENABLE_STATIC_LIBS) list(APPEND stupidAI_SRCS main.cpp StdInc.cpp) endif() assign_source_group(${stupidAI_SRCS} ${stupidAI_HEADERS}) -if(ENABLE_STATIC_AI_LIBS) +if(ENABLE_STATIC_LIBS) add_library(StupidAI STATIC ${stupidAI_SRCS} ${stupidAI_HEADERS}) else() add_library(StupidAI SHARED ${stupidAI_SRCS} ${stupidAI_HEADERS}) diff --git a/AI/VCAI/CMakeLists.txt b/AI/VCAI/CMakeLists.txt index c2400cc86..a653d27f6 100644 --- a/AI/VCAI/CMakeLists.txt +++ b/AI/VCAI/CMakeLists.txt @@ -94,12 +94,12 @@ set(VCAI_HEADERS VCAI.h ) -if(NOT ENABLE_STATIC_AI_LIBS) +if(NOT ENABLE_STATIC_LIBS) list(APPEND VCAI_SRCS main.cpp StdInc.cpp) endif() assign_source_group(${VCAI_SRCS} ${VCAI_HEADERS}) -if(ENABLE_STATIC_AI_LIBS) +if(ENABLE_STATIC_LIBS) add_library(VCAI STATIC ${VCAI_SRCS} ${VCAI_HEADERS}) else() add_library(VCAI SHARED ${VCAI_SRCS} ${VCAI_HEADERS}) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b853427d..e8d63d6c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,26 +62,22 @@ option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" O # Platform-specific options if(ANDROID) - set(ENABLE_STATIC_AI_LIBS ON) + set(ENABLE_STATIC_LIBS ON) set(ENABLE_LAUNCHER OFF) else() - option(ENABLE_STATIC_AI_LIBS "Build all libraries statically (NOT only AI)" OFF) + option(ENABLE_STATIC_LIBS "Build library and all components such as AI statically" OFF) option(ENABLE_LAUNCHER "Enable compilation of launcher" ON) endif() -if(APPLE_IOS) - set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix") - set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen") -endif() - if(APPLE_IOS OR ANDROID) - option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF) # Used for Snap packages and also useful for debugging + set(ENABLE_MONOLITHIC_INSTALL OFF) set(ENABLE_SINGLE_APP_BUILD ON) set(ENABLE_EDITOR OFF) set(ENABLE_TEST OFF) set(ENABLE_LOBBY OFF) set(COPY_CONFIG_ON_BUILD OFF) else() + option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF) # Used for Snap packages and also useful for debugging option(COPY_CONFIG_ON_BUILD "Copies config folder into output directory at building phase" ON) option(ENABLE_EDITOR "Enable compilation of map editor" ON) option(ENABLE_SINGLE_APP_BUILD "Builds client and launcher as single executable" OFF) @@ -89,6 +85,20 @@ else() option(ENABLE_LOBBY "Enable compilation of lobby server" OFF) endif() +# ERM depends on LUA implicitly +if(ENABLE_ERM AND NOT ENABLE_LUA) + set(ENABLE_LUA ON) +endif() + +############################################ +# Miscellaneous options # +############################################ + +if(APPLE_IOS) + set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix") + set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen") +endif() + if(ENABLE_COLORIZED_COMPILER_OUTPUT) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") add_compile_options(-fcolor-diagnostics) @@ -126,15 +136,6 @@ endif() set(PACKAGE_NAME_SUFFIX "" CACHE STRING "Suffix for CPack package name") set(PACKAGE_FILE_NAME "" CACHE STRING "Override for CPack package filename") -# ERM depends on LUA implicitly -if(ENABLE_ERM AND NOT ENABLE_LUA) - set(ENABLE_LUA ON) -endif() - -############################################ -# Miscellaneous options # -############################################ - set(CMAKE_MODULE_PATH ${CMAKE_HOME_DIRECTORY}/cmake_modules ${PROJECT_SOURCE_DIR}/CI) # Contains custom functions and macros, but don't altering any options diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 195843198..487f1f443 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -376,7 +376,7 @@ else() endif() add_dependencies(vcmiclient vcmiserver) -if(NOT ENABLE_STATIC_AI_LIBS) +if(NOT ENABLE_STATIC_LIBS) add_dependencies(vcmiclient BattleAI EmptyAI diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 2e66e204b..452d8556c 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,4 +1,4 @@ -if(ENABLE_STATIC_AI_LIBS) +if(ENABLE_STATIC_LIBS) add_main_lib(${VCMI_LIB_TARGET} STATIC) target_compile_definitions(${VCMI_LIB_TARGET} PRIVATE STATIC_AI) target_link_libraries(${VCMI_LIB_TARGET} PRIVATE From bb10f5a055b0a3fc9ca9257d408ef1ee858ff728 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 11 Feb 2024 00:46:47 +0200 Subject: [PATCH 083/250] Fix building with static libs enabled on gcc --- CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e8d63d6c0..1ad509087 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,6 @@ option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF) option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF) option(ENABLE_TRANSLATIONS "Enable generation of translations for launcher and editor" ON) option(ENABLE_NULLKILLER_AI "Enable compilation of Nullkiller AI library" ON) -option(ENABLE_SERVER "Enable compilation of dedicated server" ON) option(ENABLE_GITVERSION "Enable Version.cpp with Git commit hash" ON) # Compilation options @@ -75,10 +74,12 @@ if(APPLE_IOS OR ANDROID) set(ENABLE_EDITOR OFF) set(ENABLE_TEST OFF) set(ENABLE_LOBBY OFF) + set(ENABLE_SERVER OFF) set(COPY_CONFIG_ON_BUILD OFF) else() option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF) # Used for Snap packages and also useful for debugging option(COPY_CONFIG_ON_BUILD "Copies config folder into output directory at building phase" ON) + option(ENABLE_SERVER "Enable compilation of dedicated server" ON) option(ENABLE_EDITOR "Enable compilation of map editor" ON) option(ENABLE_SINGLE_APP_BUILD "Builds client and launcher as single executable" OFF) option(ENABLE_TEST "Enable compilation of unit tests" OFF) @@ -94,6 +95,10 @@ endif() # Miscellaneous options # ############################################ +if (ENABLE_STATIC_LIBS AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif() + if(APPLE_IOS) set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix") set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen") From 0fc0ad238bef1a2be72184af2478c44d86b30571 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 11 Feb 2024 15:38:29 +0200 Subject: [PATCH 084/250] Fixes for server shutdown logic, implemented connection aborting for local server --- client/CServerHandler.cpp | 47 +++++++++++++++++++++++++++++++++------ client/CServerHandler.h | 1 + client/ServerRunner.cpp | 44 +++++++++++++++--------------------- client/ServerRunner.h | 20 ++++++++++------- 4 files changed, 71 insertions(+), 41 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index fdb4f6292..17bac420d 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -177,6 +177,7 @@ INetworkHandler & CServerHandler::getNetworkHandler() void CServerHandler::startLocalServerAndConnect(bool connectToLobby) { + logNetwork->trace("\tLocal server startup has been requested"); #ifdef VCMI_MOBILE // mobile apps can't spawn separate processes - only thread mode is available serverRunner.reset(new ServerThreadRunner()); @@ -187,10 +188,11 @@ void CServerHandler::startLocalServerAndConnect(bool connectToLobby) serverRunner.reset(new ServerThreadRunner()); #endif + logNetwork->trace("\tStarting local server"); serverRunner->start(getLocalPort(), connectToLobby); + logNetwork->trace("\tConnecting to local server"); connectToServer(getLocalHostname(), getLocalPort()); - - logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff()); + logNetwork->trace("\tWaiting for connection"); } void CServerHandler::connectToServer(const std::string & addr, const ui16 port) @@ -238,6 +240,10 @@ void CServerHandler::onTimer() if(getState() == EClientState::CONNECTION_CANCELLED) { logNetwork->info("Connection aborted by player!"); + serverRunner->wait(); + serverRunner.reset(); + if (GH.windows().topWindow() != nullptr) + GH.windows().popWindows(1); return; } @@ -301,6 +307,9 @@ EClientState CServerHandler::getState() const void CServerHandler::setState(EClientState newState) { + if (newState == EClientState::CONNECTION_CANCELLED && serverRunner != nullptr) + serverRunner->shutdown(); + state = newState; } @@ -830,11 +839,7 @@ void CServerHandler::onPacketReceived(const std::shared_ptr void CServerHandler::onDisconnected(const std::shared_ptr & connection, const std::string & errorMessage) { - if (serverRunner) - { - serverRunner->wait(); - serverRunner.reset(); - } + waitForServerShutdown(); if(getState() == EClientState::DISCONNECTING) { @@ -865,6 +870,34 @@ void CServerHandler::onDisconnected(const std::shared_ptr & networkConnection.reset(); } +void CServerHandler::waitForServerShutdown() +{ + if (!serverRunner) + return; // may not exist for guest in MP + + serverRunner->wait(); + int exitCode = serverRunner->exitCode(); + serverRunner.reset(); + + if (exitCode == 0) + { + logNetwork->info("Server closed correctly"); + } + else + { + boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); + 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 log file for more info"); + } + + serverRunner.reset(); +} + void CServerHandler::visitForLobby(CPackForLobby & lobbyPack) { if(applier->getApplier(CTypeList::getInstance().getTypeID(&lobbyPack))->applyOnLobbyHandler(this, lobbyPack)) diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 7b883de22..c412dbd82 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -111,6 +111,7 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor std::atomic state; void threadRunNetwork(); + void waitForServerShutdown(); void sendLobbyPack(const CPackForLobby & pack) const override; diff --git a/client/ServerRunner.cpp b/client/ServerRunner.cpp index 891c0b134..59cce1110 100644 --- a/client/ServerRunner.cpp +++ b/client/ServerRunner.cpp @@ -22,58 +22,50 @@ ServerThreadRunner::ServerThreadRunner() = default; ServerThreadRunner::~ServerThreadRunner() = default; -ServerProcessRunner::ServerProcessRunner() = default; -ServerProcessRunner::~ServerProcessRunner() = default; void ServerThreadRunner::start(uint16_t port, bool connectToLobby) { - setThreadName("runServer"); - server = std::make_unique(port, connectToLobby, true); threadRunLocalServer = boost::thread([this]{ + setThreadName("runServer"); server->run(); }); } -void ServerThreadRunner::stop() +void ServerThreadRunner::shutdown() { server->setState(EServerState::SHUTDOWN); } -int ServerThreadRunner::wait() +void ServerThreadRunner::wait() { threadRunLocalServer.join(); +} + +int ServerThreadRunner::exitCode() +{ return 0; } -void ServerProcessRunner::stop() +#ifndef VCMI_MOBILE + +ServerProcessRunner::ServerProcessRunner() = default; +ServerProcessRunner::~ServerProcessRunner() = default; + +void ServerProcessRunner::shutdown() { child->terminate(); } -int ServerProcessRunner::wait() +void ServerProcessRunner::wait() { child->wait(); +} +int ServerProcessRunner::exitCode() +{ return child->exit_code(); - -// if (child->exit_code() == 0) -// { -// logNetwork->info("Server closed correctly"); -// } -// else -// { -// boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); -// -// 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); -// } } void ServerProcessRunner::start(uint16_t port, bool connectToLobby) @@ -93,4 +85,4 @@ void ServerProcessRunner::start(uint16_t port, bool connectToLobby) throw std::runtime_error("Failed to start server! Reason: " + ec.message()); } - +#endif diff --git a/client/ServerRunner.h b/client/ServerRunner.h index 19b73191e..115dcebfa 100644 --- a/client/ServerRunner.h +++ b/client/ServerRunner.h @@ -15,21 +15,23 @@ class IServerRunner { public: virtual void start(uint16_t port, bool connectToLobby) = 0; - virtual void stop() = 0; - virtual int wait() = 0; + virtual void shutdown() = 0; + virtual void wait() = 0; + virtual int exitCode() = 0; virtual ~IServerRunner() = default; }; -/// Server instance will run as a thread of client process +/// Class that runs server instance as a thread of client process class ServerThreadRunner : public IServerRunner, boost::noncopyable { std::unique_ptr server; boost::thread threadRunLocalServer; public: void start(uint16_t port, bool connectToLobby) override; - void stop() override; - int wait() override; + void shutdown() override; + void wait() override; + int exitCode() override; ServerThreadRunner(); ~ServerThreadRunner(); @@ -41,15 +43,17 @@ namespace boost::process { class child; } -/// Server instance will run as a separate process +/// Class that runs server instance as a child process +/// Available only on desktop systems where process management is allowed class ServerProcessRunner : public IServerRunner, boost::noncopyable { std::unique_ptr child; public: void start(uint16_t port, bool connectToLobby) override; - void stop() override; - int wait() override; + void shutdown() override; + void wait() override; + int exitCode() override; ServerProcessRunner(); ~ServerProcessRunner(); From cded8b199950fef0d0fdb7f5d9f6ba3da1927bcc Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 11 Feb 2024 16:11:21 +0200 Subject: [PATCH 085/250] Show human-readable thread name in log --- client/CMT.cpp | 4 ++++ config/schemas/settings.json | 2 +- lib/CThreadHelper.cpp | 21 ++++++++++++++++++--- lib/CThreadHelper.h | 9 ++++++++- lib/logging/CLogger.cpp | 14 ++++++++++++-- lib/logging/CLogger.h | 7 +------ 6 files changed, 44 insertions(+), 13 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index 5305a273e..63fa132e1 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -189,6 +189,7 @@ int main(int argc, char * argv[]) console->start(); #endif + setThreadNameLoggingOnly("MainGUI"); const boost::filesystem::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Client_log.txt"; logConfig = new CBasicLogConfigurator(logPath, console); logConfig->configureDefault(); @@ -397,7 +398,10 @@ void playIntro() static void mainLoop() { +#ifndef VCMI_UNIX + // on Linux, name of main thread is also name of our process. Which we don't want to change setThreadName("MainGUI"); +#endif while(1) //main SDL events loop { diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 613eb765b..890b90dfa 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -463,7 +463,7 @@ "properties" : { "format" : { "type" : "string", - "default" : "[%c] %l %n - %m" + "default" : "[%c] %l [%t] %n - %m" } } }, diff --git a/lib/CThreadHelper.cpp b/lib/CThreadHelper.cpp index fd98e279b..5f5393c4b 100644 --- a/lib/CThreadHelper.cpp +++ b/lib/CThreadHelper.cpp @@ -55,10 +55,25 @@ void CThreadHelper::processTasks() } } -// set name for this thread. -// NOTE: on *nix string will be trimmed to 16 symbols +static thread_local std::string threadNameForLogging; + +std::string getThreadName() +{ + if (!threadNameForLogging.empty()) + return threadNameForLogging; + + return boost::lexical_cast(boost::this_thread::get_id()); +} + +void setThreadNameLoggingOnly(const std::string &name) +{ + threadNameForLogging = name; +} + void setThreadName(const std::string &name) { + threadNameForLogging = name; + #ifdef VCMI_WINDOWS #ifndef __GNUC__ //follows http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx @@ -90,7 +105,7 @@ void setThreadName(const std::string &name) //not supported #endif -#elif defined(__linux__) +#elif defined(VCMI_UNIX) prctl(PR_SET_NAME, name.c_str(), 0, 0, 0); #elif defined(VCMI_APPLE) pthread_setname_np(name.c_str()); diff --git a/lib/CThreadHelper.h b/lib/CThreadHelper.h index 80f73206f..d9c125f25 100644 --- a/lib/CThreadHelper.h +++ b/lib/CThreadHelper.h @@ -85,7 +85,14 @@ private: } }; - +/// Sets thread name that will be used for both logs and debugger (if supported) +/// WARNING: on Unix-like systems this method should not be used for main thread since it will also change name of the process void DLL_LINKAGE setThreadName(const std::string &name); +/// Sets thread name for use in logging only +void DLL_LINKAGE setThreadNameLoggingOnly(const std::string &name); + +/// Returns human-readable thread name that was set before, or string form of system-provided thread ID if no human-readable name was set +std::string DLL_LINKAGE getThreadName(); + VCMI_LIB_NAMESPACE_END diff --git a/lib/logging/CLogger.cpp b/lib/logging/CLogger.cpp index 2f9ccdf6f..aff64cea3 100644 --- a/lib/logging/CLogger.cpp +++ b/lib/logging/CLogger.cpp @@ -9,6 +9,7 @@ */ #include "StdInc.h" #include "CLogger.h" +#include "../CThreadHelper.h" #ifdef VCMI_ANDROID #include @@ -427,8 +428,7 @@ void CLogConsoleTarget::setColorMapping(const CColorMapping & colorMapping) { th CLogFileTarget::CLogFileTarget(const boost::filesystem::path & filePath, bool append): file(filePath.c_str(), append ? std::ios_base::app : std::ios_base::out) { -// formatter.setPattern("%d %l %n [%t] - %m"); - formatter.setPattern("%l %n [%t] - %m"); + formatter.setPattern("[%c] %l %n [%t] - %m"); } void CLogFileTarget::write(const LogRecord & record) @@ -446,4 +446,14 @@ CLogFileTarget::~CLogFileTarget() file.close(); } +LogRecord::LogRecord(const CLoggerDomain & domain, ELogLevel::ELogLevel level, const std::string & message) + : domain(domain), + level(level), + message(message), + timeStamp(boost::posix_time::microsec_clock::local_time()), + threadId(getThreadName()) +{ + +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/logging/CLogger.h b/lib/logging/CLogger.h index 72a0b929e..22c3035f1 100644 --- a/lib/logging/CLogger.h +++ b/lib/logging/CLogger.h @@ -107,12 +107,7 @@ private: /// The struct LogRecord holds the log message and additional logging information. struct DLL_LINKAGE LogRecord { - LogRecord(const CLoggerDomain & domain, ELogLevel::ELogLevel level, const std::string & message) - : domain(domain), - level(level), - message(message), - timeStamp(boost::posix_time::microsec_clock::local_time()), - threadId(boost::lexical_cast(boost::this_thread::get_id())) { } + LogRecord(const CLoggerDomain & domain, ELogLevel::ELogLevel level, const std::string & message); CLoggerDomain domain; ELogLevel::ELogLevel level; From 19ccef713190c97495b7b4cfb8c3eae10f829db0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 11 Feb 2024 19:28:18 +0200 Subject: [PATCH 086/250] Fix build --- client/CMakeLists.txt | 2 -- lib/CThreadHelper.cpp | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 487f1f443..b8641baaa 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -375,7 +375,6 @@ else() add_executable(vcmiclient ${client_SRCS} ${client_HEADERS}) endif() -add_dependencies(vcmiclient vcmiserver) if(NOT ENABLE_STATIC_LIBS) add_dependencies(vcmiclient BattleAI @@ -454,7 +453,6 @@ elseif(APPLE_IOS) endif() target_link_libraries(vcmiclient PRIVATE vcmiservercommon) - if(ENABLE_SINGLE_APP_BUILD AND ENABLE_LAUNCHER) target_link_libraries(vcmiclient PRIVATE vcmilauncher) endif() diff --git a/lib/CThreadHelper.cpp b/lib/CThreadHelper.cpp index 5f5393c4b..21f8dc4bb 100644 --- a/lib/CThreadHelper.cpp +++ b/lib/CThreadHelper.cpp @@ -105,12 +105,12 @@ void setThreadName(const std::string &name) //not supported #endif -#elif defined(VCMI_UNIX) - prctl(PR_SET_NAME, name.c_str(), 0, 0, 0); #elif defined(VCMI_APPLE) pthread_setname_np(name.c_str()); #elif defined(VCMI_HAIKU) rename_thread(find_thread(NULL), name.c_str()); +#elif defined(VCMI_UNIX) + prctl(PR_SET_NAME, name.c_str(), 0, 0, 0); #endif } From 30d677724bb576f3df38474f9ed02ba2895ea1c4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 11 Feb 2024 20:16:44 +0200 Subject: [PATCH 087/250] Remove no longer used methods from Java wrapper --- .../main/java/eu/vcmi/vcmi/NativeMethods.java | 62 ------------------- .../java/eu/vcmi/vcmi/VcmiSDLActivity.java | 24 ------- 2 files changed, 86 deletions(-) diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java index efe4a2be5..50a821563 100644 --- a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java +++ b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java @@ -36,12 +36,6 @@ public class NativeMethods public static native void initClassloader(); - public static native void createServer(); - - public static native void notifyServerReady(); - - public static native void notifyServerClosed(); - public static native boolean tryToSaveTheGame(); public static void setupMsg(final Messenger msg) @@ -77,62 +71,6 @@ public class NativeMethods return ctx.getApplicationInfo().nativeLibraryDir; } - @SuppressWarnings(Const.JNI_METHOD_SUPPRESS) - public static void startServer() - { - Log.i("Got server create request"); - final Context ctx = SDL.getContext(); - - if (!(ctx instanceof VcmiSDLActivity)) - { - Log.e("Unexpected context... " + ctx); - return; - } - - Intent intent = new Intent(ctx, SDLActivity.class); - intent.setAction(VcmiSDLActivity.NATIVE_ACTION_CREATE_SERVER); - // I probably do something incorrectly, but sending new intent to the activity "normally" breaks SDL events handling (probably detaches jnienv?) - // so instead let's call onNewIntent directly, as out context SHOULD be SDLActivity anyway - ((VcmiSDLActivity) ctx).hackCallNewIntentDirectly(intent); -// ctx.startActivity(intent); - } - - @SuppressWarnings(Const.JNI_METHOD_SUPPRESS) - public static void killServer() - { - Log.i("Got server close request"); - - final Context ctx = SDL.getContext(); - ctx.stopService(new Intent(ctx, ServerService.class)); - - Messenger messenger = requireServerMessenger(); - try - { - // we need to actually inform client about killing the server, beacuse it needs to unbind service connection before server gets destroyed - messenger.send(Message.obtain(null, VcmiSDLActivity.SERVER_MESSAGE_SERVER_KILLED)); - } - catch (RemoteException e) - { - Log.w("Connection with client process broken?"); - } - } - - @SuppressWarnings(Const.JNI_METHOD_SUPPRESS) - public static void onServerReady() - { - Log.i("Got server ready msg"); - Messenger messenger = requireServerMessenger(); - - try - { - messenger.send(Message.obtain(null, VcmiSDLActivity.SERVER_MESSAGE_SERVER_READY)); - } - catch (RemoteException e) - { - Log.w("Connection with client process broken?"); - } - } - @SuppressWarnings(Const.JNI_METHOD_SUPPRESS) public static void showProgress() { diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java index 056321c53..035bf3265 100644 --- a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java +++ b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java @@ -20,9 +20,6 @@ import eu.vcmi.vcmi.util.Log; public class VcmiSDLActivity extends SDLActivity { - public static final int SERVER_MESSAGE_SERVER_READY = 1000; - public static final int SERVER_MESSAGE_SERVER_KILLED = 1001; - public static final String NATIVE_ACTION_CREATE_SERVER = "SDLActivity.Action.CreateServer"; protected static final int COMMAND_USER = 0x8000; final Messenger mClientMessenger = new Messenger( @@ -188,26 +185,5 @@ public class VcmiSDLActivity extends SDLActivity { mCallback = callback; } - - @Override - public void handleMessage(Message msg) - { - Log.i(this, "Got server msg " + msg); - switch (msg.what) - { - case SERVER_MESSAGE_SERVER_READY: - NativeMethods.notifyServerReady(); - break; - case SERVER_MESSAGE_SERVER_KILLED: - if (mCallback != null) - { - mCallback.unbindServer(); - } - NativeMethods.notifyServerClosed(); - break; - default: - super.handleMessage(msg); - } - } } } \ No newline at end of file From a909d7ddde046cdb8e46bbc3142377cfe333eaca Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 11 Feb 2024 20:38:24 +0200 Subject: [PATCH 088/250] Removed cmake_modules/VCMI_lib macro, use it directly in place --- AI/BattleAI/CMakeLists.txt | 2 +- AI/EmptyAI/CMakeLists.txt | 2 +- AI/Nullkiller/CMakeLists.txt | 2 +- AI/StupidAI/CMakeLists.txt | 2 +- AI/VCAI/CMakeLists.txt | 2 +- CMakeLists.txt | 2 - client/CMakeLists.txt | 2 +- cmake_modules/VCMI_lib.cmake | 757 ---------------------------------- launcher/CMakeLists.txt | 2 +- lib/CMakeLists.txt | 767 ++++++++++++++++++++++++++++++++++- mapeditor/CMakeLists.txt | 2 +- scripting/erm/CMakeLists.txt | 2 +- scripting/lua/CMakeLists.txt | 2 +- 13 files changed, 770 insertions(+), 776 deletions(-) delete mode 100644 cmake_modules/VCMI_lib.cmake diff --git a/AI/BattleAI/CMakeLists.txt b/AI/BattleAI/CMakeLists.txt index 1ce31e7da..1e1b4c625 100644 --- a/AI/BattleAI/CMakeLists.txt +++ b/AI/BattleAI/CMakeLists.txt @@ -39,7 +39,7 @@ else() endif() target_include_directories(BattleAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(BattleAI PRIVATE ${VCMI_LIB_TARGET} TBB::tbb) +target_link_libraries(BattleAI PRIVATE vcmi TBB::tbb) vcmi_set_output_dir(BattleAI "AI") enable_pch(BattleAI) diff --git a/AI/EmptyAI/CMakeLists.txt b/AI/EmptyAI/CMakeLists.txt index a463f173b..3988f41a7 100644 --- a/AI/EmptyAI/CMakeLists.txt +++ b/AI/EmptyAI/CMakeLists.txt @@ -21,7 +21,7 @@ else() endif() target_include_directories(EmptyAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(EmptyAI PRIVATE ${VCMI_LIB_TARGET}) +target_link_libraries(EmptyAI PRIVATE vcmi) vcmi_set_output_dir(EmptyAI "AI") enable_pch(EmptyAI) diff --git a/AI/Nullkiller/CMakeLists.txt b/AI/Nullkiller/CMakeLists.txt index b775630f3..8ea5f777a 100644 --- a/AI/Nullkiller/CMakeLists.txt +++ b/AI/Nullkiller/CMakeLists.txt @@ -138,7 +138,7 @@ else() endif() target_include_directories(Nullkiller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(Nullkiller PUBLIC ${VCMI_LIB_TARGET} fuzzylite::fuzzylite TBB::tbb) +target_link_libraries(Nullkiller PUBLIC vcmi fuzzylite::fuzzylite TBB::tbb) vcmi_set_output_dir(Nullkiller "AI") enable_pch(Nullkiller) diff --git a/AI/StupidAI/CMakeLists.txt b/AI/StupidAI/CMakeLists.txt index 5177a2561..013364cdf 100644 --- a/AI/StupidAI/CMakeLists.txt +++ b/AI/StupidAI/CMakeLists.txt @@ -20,7 +20,7 @@ else() install(TARGETS StupidAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR}) endif() -target_link_libraries(StupidAI PRIVATE ${VCMI_LIB_TARGET}) +target_link_libraries(StupidAI PRIVATE vcmi) target_include_directories(StupidAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) vcmi_set_output_dir(StupidAI "AI") diff --git a/AI/VCAI/CMakeLists.txt b/AI/VCAI/CMakeLists.txt index a653d27f6..29c621947 100644 --- a/AI/VCAI/CMakeLists.txt +++ b/AI/VCAI/CMakeLists.txt @@ -107,7 +107,7 @@ else() endif() target_include_directories(VCAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(VCAI PUBLIC ${VCMI_LIB_TARGET} fuzzylite::fuzzylite) +target_link_libraries(VCAI PUBLIC vcmi fuzzylite::fuzzylite) vcmi_set_output_dir(VCAI "AI") enable_pch(VCAI) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ad509087..3040e89a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -608,10 +608,8 @@ if(APPLE_IOS) add_subdirectory(ios) endif() -set(VCMI_LIB_TARGET vcmi) add_subdirectory_with_folder("AI" AI) -include(VCMI_lib) add_subdirectory(lib) add_subdirectory(server) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index b8641baaa..545faa4f5 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -458,7 +458,7 @@ if(ENABLE_SINGLE_APP_BUILD AND ENABLE_LAUNCHER) endif() target_link_libraries(vcmiclient PRIVATE - ${VCMI_LIB_TARGET} SDL2::SDL2 SDL2::Image SDL2::Mixer SDL2::TTF + vcmi SDL2::SDL2 SDL2::Image SDL2::Mixer SDL2::TTF ) if(ffmpeg_LIBRARIES) diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake deleted file mode 100644 index 63599b2cc..000000000 --- a/cmake_modules/VCMI_lib.cmake +++ /dev/null @@ -1,757 +0,0 @@ -macro(add_main_lib TARGET_NAME LIBRARY_TYPE) - if(NOT DEFINED MAIN_LIB_DIR) - set(MAIN_LIB_DIR "${CMAKE_SOURCE_DIR}/lib") - endif() - - set(lib_SRCS - ${MAIN_LIB_DIR}/StdInc.cpp - - ${MAIN_LIB_DIR}/battle/AccessibilityInfo.cpp - ${MAIN_LIB_DIR}/battle/BattleAction.cpp - ${MAIN_LIB_DIR}/battle/BattleAttackInfo.cpp - ${MAIN_LIB_DIR}/battle/BattleHex.cpp - ${MAIN_LIB_DIR}/battle/BattleInfo.cpp - ${MAIN_LIB_DIR}/battle/BattleProxy.cpp - ${MAIN_LIB_DIR}/battle/BattleStateInfoForRetreat.cpp - ${MAIN_LIB_DIR}/battle/CBattleInfoCallback.cpp - ${MAIN_LIB_DIR}/battle/CBattleInfoEssentials.cpp - ${MAIN_LIB_DIR}/battle/CObstacleInstance.cpp - ${MAIN_LIB_DIR}/battle/CPlayerBattleCallback.cpp - ${MAIN_LIB_DIR}/battle/CUnitState.cpp - ${MAIN_LIB_DIR}/battle/DamageCalculator.cpp - ${MAIN_LIB_DIR}/battle/Destination.cpp - ${MAIN_LIB_DIR}/battle/IBattleState.cpp - ${MAIN_LIB_DIR}/battle/ReachabilityInfo.cpp - ${MAIN_LIB_DIR}/battle/SideInBattle.cpp - ${MAIN_LIB_DIR}/battle/SiegeInfo.cpp - ${MAIN_LIB_DIR}/battle/Unit.cpp - - ${MAIN_LIB_DIR}/bonuses/Bonus.cpp - ${MAIN_LIB_DIR}/bonuses/BonusEnum.cpp - ${MAIN_LIB_DIR}/bonuses/BonusList.cpp - ${MAIN_LIB_DIR}/bonuses/BonusParams.cpp - ${MAIN_LIB_DIR}/bonuses/BonusSelector.cpp - ${MAIN_LIB_DIR}/bonuses/BonusCustomTypes.cpp - ${MAIN_LIB_DIR}/bonuses/CBonusProxy.cpp - ${MAIN_LIB_DIR}/bonuses/CBonusSystemNode.cpp - ${MAIN_LIB_DIR}/bonuses/IBonusBearer.cpp - ${MAIN_LIB_DIR}/bonuses/Limiters.cpp - ${MAIN_LIB_DIR}/bonuses/Propagators.cpp - ${MAIN_LIB_DIR}/bonuses/Updaters.cpp - - ${MAIN_LIB_DIR}/campaign/CampaignHandler.cpp - ${MAIN_LIB_DIR}/campaign/CampaignState.cpp - - ${MAIN_LIB_DIR}/constants/EntityIdentifiers.cpp - - ${MAIN_LIB_DIR}/events/ApplyDamage.cpp - ${MAIN_LIB_DIR}/events/GameResumed.cpp - ${MAIN_LIB_DIR}/events/ObjectVisitEnded.cpp - ${MAIN_LIB_DIR}/events/ObjectVisitStarted.cpp - ${MAIN_LIB_DIR}/events/PlayerGotTurn.cpp - ${MAIN_LIB_DIR}/events/TurnStarted.cpp - - ${MAIN_LIB_DIR}/filesystem/AdapterLoaders.cpp - ${MAIN_LIB_DIR}/filesystem/CArchiveLoader.cpp - ${MAIN_LIB_DIR}/filesystem/CBinaryReader.cpp - ${MAIN_LIB_DIR}/filesystem/CCompressedStream.cpp - ${MAIN_LIB_DIR}/filesystem/CFileInputStream.cpp - ${MAIN_LIB_DIR}/filesystem/CFilesystemLoader.cpp - ${MAIN_LIB_DIR}/filesystem/CMemoryBuffer.cpp - ${MAIN_LIB_DIR}/filesystem/CMemoryStream.cpp - ${MAIN_LIB_DIR}/filesystem/CZipLoader.cpp - ${MAIN_LIB_DIR}/filesystem/CZipSaver.cpp - ${MAIN_LIB_DIR}/filesystem/FileInfo.cpp - ${MAIN_LIB_DIR}/filesystem/Filesystem.cpp - ${MAIN_LIB_DIR}/filesystem/MinizipExtensions.cpp - ${MAIN_LIB_DIR}/filesystem/ResourcePath.cpp - - ${MAIN_LIB_DIR}/gameState/CGameState.cpp - ${MAIN_LIB_DIR}/gameState/CGameStateCampaign.cpp - ${MAIN_LIB_DIR}/gameState/InfoAboutArmy.cpp - ${MAIN_LIB_DIR}/gameState/TavernHeroesPool.cpp - - ${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.cpp - ${MAIN_LIB_DIR}/logging/CLogger.cpp - - ${MAIN_LIB_DIR}/mapObjectConstructors/AObjectTypeHandler.cpp - ${MAIN_LIB_DIR}/mapObjectConstructors/CBankInstanceConstructor.cpp - ${MAIN_LIB_DIR}/mapObjectConstructors/CObjectClassesHandler.cpp - ${MAIN_LIB_DIR}/mapObjectConstructors/CommonConstructors.cpp - ${MAIN_LIB_DIR}/mapObjectConstructors/CRewardableConstructor.cpp - ${MAIN_LIB_DIR}/mapObjectConstructors/DwellingInstanceConstructor.cpp - ${MAIN_LIB_DIR}/mapObjectConstructors/HillFortInstanceConstructor.cpp - ${MAIN_LIB_DIR}/mapObjectConstructors/ShipyardInstanceConstructor.cpp - - ${MAIN_LIB_DIR}/mapObjects/CArmedInstance.cpp - ${MAIN_LIB_DIR}/mapObjects/CBank.cpp - ${MAIN_LIB_DIR}/mapObjects/CGCreature.cpp - ${MAIN_LIB_DIR}/mapObjects/CGDwelling.cpp - ${MAIN_LIB_DIR}/mapObjects/CGHeroInstance.cpp - ${MAIN_LIB_DIR}/mapObjects/CGMarket.cpp - ${MAIN_LIB_DIR}/mapObjects/CGObjectInstance.cpp - ${MAIN_LIB_DIR}/mapObjects/CGPandoraBox.cpp - ${MAIN_LIB_DIR}/mapObjects/CGTownBuilding.cpp - ${MAIN_LIB_DIR}/mapObjects/CGTownInstance.cpp - ${MAIN_LIB_DIR}/mapObjects/CObjectHandler.cpp - ${MAIN_LIB_DIR}/mapObjects/CQuest.cpp - ${MAIN_LIB_DIR}/mapObjects/CRewardableObject.cpp - ${MAIN_LIB_DIR}/mapObjects/IMarket.cpp - ${MAIN_LIB_DIR}/mapObjects/IObjectInterface.cpp - ${MAIN_LIB_DIR}/mapObjects/MiscObjects.cpp - ${MAIN_LIB_DIR}/mapObjects/ObjectTemplate.cpp - - ${MAIN_LIB_DIR}/mapping/CDrawRoadsOperation.cpp - ${MAIN_LIB_DIR}/mapping/CMap.cpp - ${MAIN_LIB_DIR}/mapping/CMapHeader.cpp - ${MAIN_LIB_DIR}/mapping/CMapEditManager.cpp - ${MAIN_LIB_DIR}/mapping/CMapInfo.cpp - ${MAIN_LIB_DIR}/mapping/CMapOperation.cpp - ${MAIN_LIB_DIR}/mapping/CMapService.cpp - ${MAIN_LIB_DIR}/mapping/MapEditUtils.cpp - ${MAIN_LIB_DIR}/mapping/MapIdentifiersH3M.cpp - ${MAIN_LIB_DIR}/mapping/MapFeaturesH3M.cpp - ${MAIN_LIB_DIR}/mapping/MapFormatH3M.cpp - ${MAIN_LIB_DIR}/mapping/MapReaderH3M.cpp - ${MAIN_LIB_DIR}/mapping/MapFormatJson.cpp - ${MAIN_LIB_DIR}/mapping/ObstacleProxy.cpp - - ${MAIN_LIB_DIR}/modding/ActiveModsInSaveList.cpp - ${MAIN_LIB_DIR}/modding/CModHandler.cpp - ${MAIN_LIB_DIR}/modding/CModInfo.cpp - ${MAIN_LIB_DIR}/modding/CModVersion.cpp - ${MAIN_LIB_DIR}/modding/ContentTypeHandler.cpp - ${MAIN_LIB_DIR}/modding/IdentifierStorage.cpp - ${MAIN_LIB_DIR}/modding/ModUtility.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 - - ${MAIN_LIB_DIR}/pathfinder/CGPathNode.cpp - ${MAIN_LIB_DIR}/pathfinder/CPathfinder.cpp - ${MAIN_LIB_DIR}/pathfinder/NodeStorage.cpp - ${MAIN_LIB_DIR}/pathfinder/PathfinderOptions.cpp - ${MAIN_LIB_DIR}/pathfinder/PathfindingRules.cpp - ${MAIN_LIB_DIR}/pathfinder/TurnInfo.cpp - - ${MAIN_LIB_DIR}/rewardable/Configuration.cpp - ${MAIN_LIB_DIR}/rewardable/Info.cpp - ${MAIN_LIB_DIR}/rewardable/Interface.cpp - ${MAIN_LIB_DIR}/rewardable/Limiter.cpp - ${MAIN_LIB_DIR}/rewardable/Reward.cpp - - ${MAIN_LIB_DIR}/rmg/RmgArea.cpp - ${MAIN_LIB_DIR}/rmg/RmgObject.cpp - ${MAIN_LIB_DIR}/rmg/RmgPath.cpp - ${MAIN_LIB_DIR}/rmg/CMapGenerator.cpp - ${MAIN_LIB_DIR}/rmg/CMapGenOptions.cpp - ${MAIN_LIB_DIR}/rmg/CRmgTemplate.cpp - ${MAIN_LIB_DIR}/rmg/CRmgTemplateStorage.cpp - ${MAIN_LIB_DIR}/rmg/CZonePlacer.cpp - ${MAIN_LIB_DIR}/rmg/TileInfo.cpp - ${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 - ${MAIN_LIB_DIR}/rmg/modificators/RoadPlacer.cpp - ${MAIN_LIB_DIR}/rmg/modificators/TreasurePlacer.cpp - ${MAIN_LIB_DIR}/rmg/modificators/PrisonHeroPlacer.cpp - ${MAIN_LIB_DIR}/rmg/modificators/QuestArtifactPlacer.cpp - ${MAIN_LIB_DIR}/rmg/modificators/ConnectionsPlacer.cpp - ${MAIN_LIB_DIR}/rmg/modificators/WaterAdopter.cpp - ${MAIN_LIB_DIR}/rmg/modificators/MinePlacer.cpp - ${MAIN_LIB_DIR}/rmg/modificators/TownPlacer.cpp - ${MAIN_LIB_DIR}/rmg/modificators/WaterProxy.cpp - ${MAIN_LIB_DIR}/rmg/modificators/WaterRoutes.cpp - ${MAIN_LIB_DIR}/rmg/modificators/RockPlacer.cpp - ${MAIN_LIB_DIR}/rmg/modificators/RockFiller.cpp - ${MAIN_LIB_DIR}/rmg/modificators/ObstaclePlacer.cpp - ${MAIN_LIB_DIR}/rmg/modificators/RiverPlacer.cpp - ${MAIN_LIB_DIR}/rmg/modificators/TerrainPainter.cpp - ${MAIN_LIB_DIR}/rmg/threadpool/MapProxy.cpp - - ${MAIN_LIB_DIR}/serializer/BinaryDeserializer.cpp - ${MAIN_LIB_DIR}/serializer/BinarySerializer.cpp - ${MAIN_LIB_DIR}/serializer/CLoadFile.cpp - ${MAIN_LIB_DIR}/serializer/CMemorySerializer.cpp - ${MAIN_LIB_DIR}/serializer/Connection.cpp - ${MAIN_LIB_DIR}/serializer/CSaveFile.cpp - ${MAIN_LIB_DIR}/serializer/CSerializer.cpp - ${MAIN_LIB_DIR}/serializer/CTypeList.cpp - ${MAIN_LIB_DIR}/serializer/JsonDeserializer.cpp - ${MAIN_LIB_DIR}/serializer/JsonSerializeFormat.cpp - ${MAIN_LIB_DIR}/serializer/JsonSerializer.cpp - ${MAIN_LIB_DIR}/serializer/JsonUpdater.cpp - - ${MAIN_LIB_DIR}/spells/AbilityCaster.cpp - ${MAIN_LIB_DIR}/spells/AdventureSpellMechanics.cpp - ${MAIN_LIB_DIR}/spells/BattleSpellMechanics.cpp - ${MAIN_LIB_DIR}/spells/BonusCaster.cpp - ${MAIN_LIB_DIR}/spells/CSpellHandler.cpp - ${MAIN_LIB_DIR}/spells/ExternalCaster.cpp - ${MAIN_LIB_DIR}/spells/ISpellMechanics.cpp - ${MAIN_LIB_DIR}/spells/ObstacleCasterProxy.cpp - ${MAIN_LIB_DIR}/spells/Problem.cpp - ${MAIN_LIB_DIR}/spells/ProxyCaster.cpp - ${MAIN_LIB_DIR}/spells/TargetCondition.cpp - ${MAIN_LIB_DIR}/spells/ViewSpellInt.cpp - - ${MAIN_LIB_DIR}/spells/effects/Catapult.cpp - ${MAIN_LIB_DIR}/spells/effects/Clone.cpp - ${MAIN_LIB_DIR}/spells/effects/Damage.cpp - ${MAIN_LIB_DIR}/spells/effects/DemonSummon.cpp - ${MAIN_LIB_DIR}/spells/effects/Dispel.cpp - ${MAIN_LIB_DIR}/spells/effects/Effect.cpp - ${MAIN_LIB_DIR}/spells/effects/Effects.cpp - ${MAIN_LIB_DIR}/spells/effects/Heal.cpp - ${MAIN_LIB_DIR}/spells/effects/LocationEffect.cpp - ${MAIN_LIB_DIR}/spells/effects/Moat.cpp - ${MAIN_LIB_DIR}/spells/effects/Obstacle.cpp - ${MAIN_LIB_DIR}/spells/effects/Registry.cpp - ${MAIN_LIB_DIR}/spells/effects/UnitEffect.cpp - ${MAIN_LIB_DIR}/spells/effects/Summon.cpp - ${MAIN_LIB_DIR}/spells/effects/Teleport.cpp - ${MAIN_LIB_DIR}/spells/effects/Timed.cpp - ${MAIN_LIB_DIR}/spells/effects/RemoveObstacle.cpp - ${MAIN_LIB_DIR}/spells/effects/Sacrifice.cpp - - ${MAIN_LIB_DIR}/vstd/DateUtils.cpp - ${MAIN_LIB_DIR}/vstd/StringUtils.cpp - - ${MAIN_LIB_DIR}/ArtifactUtils.cpp - ${MAIN_LIB_DIR}/BasicTypes.cpp - ${MAIN_LIB_DIR}/BattleFieldHandler.cpp - ${MAIN_LIB_DIR}/CAndroidVMHelper.cpp - ${MAIN_LIB_DIR}/CArtHandler.cpp - ${MAIN_LIB_DIR}/CArtifactInstance.cpp - ${MAIN_LIB_DIR}/CBonusTypeHandler.cpp - ${MAIN_LIB_DIR}/CBuildingHandler.cpp - ${MAIN_LIB_DIR}/CConfigHandler.cpp - ${MAIN_LIB_DIR}/CConsoleHandler.cpp - ${MAIN_LIB_DIR}/CCreatureHandler.cpp - ${MAIN_LIB_DIR}/CCreatureSet.cpp - ${MAIN_LIB_DIR}/CGameInfoCallback.cpp - ${MAIN_LIB_DIR}/CGameInterface.cpp - ${MAIN_LIB_DIR}/CGeneralTextHandler.cpp - ${MAIN_LIB_DIR}/CHeroHandler.cpp - ${MAIN_LIB_DIR}/CPlayerState.cpp - ${MAIN_LIB_DIR}/CRandomGenerator.cpp - ${MAIN_LIB_DIR}/CScriptingModule.cpp - ${MAIN_LIB_DIR}/CSkillHandler.cpp - ${MAIN_LIB_DIR}/CStack.cpp - ${MAIN_LIB_DIR}/CThreadHelper.cpp - ${MAIN_LIB_DIR}/CTownHandler.cpp - ${MAIN_LIB_DIR}/GameSettings.cpp - ${MAIN_LIB_DIR}/IGameCallback.cpp - ${MAIN_LIB_DIR}/IHandlerBase.cpp - ${MAIN_LIB_DIR}/JsonDetail.cpp - ${MAIN_LIB_DIR}/JsonNode.cpp - ${MAIN_LIB_DIR}/JsonRandom.cpp - ${MAIN_LIB_DIR}/LoadProgress.cpp - ${MAIN_LIB_DIR}/LogicalExpression.cpp - ${MAIN_LIB_DIR}/MetaString.cpp - ${MAIN_LIB_DIR}/ObstacleHandler.cpp - ${MAIN_LIB_DIR}/StartInfo.cpp - ${MAIN_LIB_DIR}/ResourceSet.cpp - ${MAIN_LIB_DIR}/RiverHandler.cpp - ${MAIN_LIB_DIR}/RoadHandler.cpp - ${MAIN_LIB_DIR}/ScriptHandler.cpp - ${MAIN_LIB_DIR}/TerrainHandler.cpp - ${MAIN_LIB_DIR}/TextOperations.cpp - ${MAIN_LIB_DIR}/TurnTimerInfo.cpp - ${MAIN_LIB_DIR}/VCMIDirs.cpp - ${MAIN_LIB_DIR}/VCMI_Lib.cpp - ) - - # Version.cpp is a generated file - if(ENABLE_GITVERSION) - list(APPEND lib_SRCS ${CMAKE_BINARY_DIR}/Version.cpp) - set_source_files_properties(${CMAKE_BINARY_DIR}/Version.cpp - PROPERTIES GENERATED TRUE - ) - endif() - - set(lib_HEADERS - ${MAIN_LIB_DIR}/../include/vstd/CLoggerBase.h - ${MAIN_LIB_DIR}/../Global.h - ${MAIN_LIB_DIR}/../AUTHORS.h - ${MAIN_LIB_DIR}/StdInc.h - - ${MAIN_LIB_DIR}/../include/vstd/ContainerUtils.h - ${MAIN_LIB_DIR}/../include/vstd/RNG.h - ${MAIN_LIB_DIR}/../include/vstd/DateUtils.h - ${MAIN_LIB_DIR}/../include/vstd/StringUtils.h - - ${MAIN_LIB_DIR}/../include/vcmi/events/AdventureEvents.h - ${MAIN_LIB_DIR}/../include/vcmi/events/ApplyDamage.h - ${MAIN_LIB_DIR}/../include/vcmi/events/BattleEvents.h - ${MAIN_LIB_DIR}/../include/vcmi/events/Event.h - ${MAIN_LIB_DIR}/../include/vcmi/events/EventBus.h - ${MAIN_LIB_DIR}/../include/vcmi/events/GameResumed.h - ${MAIN_LIB_DIR}/../include/vcmi/events/GenericEvents.h - ${MAIN_LIB_DIR}/../include/vcmi/events/ObjectVisitEnded.h - ${MAIN_LIB_DIR}/../include/vcmi/events/ObjectVisitStarted.h - ${MAIN_LIB_DIR}/../include/vcmi/events/PlayerGotTurn.h - ${MAIN_LIB_DIR}/../include/vcmi/events/SubscriptionRegistry.h - ${MAIN_LIB_DIR}/../include/vcmi/events/TurnStarted.h - - ${MAIN_LIB_DIR}/../include/vcmi/scripting/Service.h - - ${MAIN_LIB_DIR}/../include/vcmi/spells/Caster.h - ${MAIN_LIB_DIR}/../include/vcmi/spells/Magic.h - ${MAIN_LIB_DIR}/../include/vcmi/spells/Service.h - ${MAIN_LIB_DIR}/../include/vcmi/spells/Spell.h - - ${MAIN_LIB_DIR}/../include/vcmi/Artifact.h - ${MAIN_LIB_DIR}/../include/vcmi/ArtifactService.h - ${MAIN_LIB_DIR}/../include/vcmi/Creature.h - ${MAIN_LIB_DIR}/../include/vcmi/CreatureService.h - ${MAIN_LIB_DIR}/../include/vcmi/Entity.h - ${MAIN_LIB_DIR}/../include/vcmi/Environment.h - ${MAIN_LIB_DIR}/../include/vcmi/Faction.h - ${MAIN_LIB_DIR}/../include/vcmi/FactionService.h - ${MAIN_LIB_DIR}/../include/vcmi/HeroClass.h - ${MAIN_LIB_DIR}/../include/vcmi/HeroClassService.h - ${MAIN_LIB_DIR}/../include/vcmi/HeroType.h - ${MAIN_LIB_DIR}/../include/vcmi/HeroTypeService.h - ${MAIN_LIB_DIR}/../include/vcmi/Metatype.h - ${MAIN_LIB_DIR}/../include/vcmi/Player.h - ${MAIN_LIB_DIR}/../include/vcmi/ServerCallback.h - ${MAIN_LIB_DIR}/../include/vcmi/Services.h - ${MAIN_LIB_DIR}/../include/vcmi/Skill.h - ${MAIN_LIB_DIR}/../include/vcmi/SkillService.h - ${MAIN_LIB_DIR}/../include/vcmi/Team.h - - ${MAIN_LIB_DIR}/battle/AccessibilityInfo.h - ${MAIN_LIB_DIR}/battle/AutocombatPreferences.h - ${MAIN_LIB_DIR}/battle/BattleAction.h - ${MAIN_LIB_DIR}/battle/BattleAttackInfo.h - ${MAIN_LIB_DIR}/battle/BattleHex.h - ${MAIN_LIB_DIR}/battle/BattleInfo.h - ${MAIN_LIB_DIR}/battle/BattleStateInfoForRetreat.h - ${MAIN_LIB_DIR}/battle/BattleProxy.h - ${MAIN_LIB_DIR}/battle/CBattleInfoCallback.h - ${MAIN_LIB_DIR}/battle/CBattleInfoEssentials.h - ${MAIN_LIB_DIR}/battle/CObstacleInstance.h - ${MAIN_LIB_DIR}/battle/CPlayerBattleCallback.h - ${MAIN_LIB_DIR}/battle/CUnitState.h - ${MAIN_LIB_DIR}/battle/DamageCalculator.h - ${MAIN_LIB_DIR}/battle/Destination.h - ${MAIN_LIB_DIR}/battle/IBattleInfoCallback.h - ${MAIN_LIB_DIR}/battle/IBattleState.h - ${MAIN_LIB_DIR}/battle/IUnitInfo.h - ${MAIN_LIB_DIR}/battle/PossiblePlayerBattleAction.h - ${MAIN_LIB_DIR}/battle/ReachabilityInfo.h - ${MAIN_LIB_DIR}/battle/SideInBattle.h - ${MAIN_LIB_DIR}/battle/SiegeInfo.h - ${MAIN_LIB_DIR}/battle/Unit.h - - ${MAIN_LIB_DIR}/bonuses/Bonus.h - ${MAIN_LIB_DIR}/bonuses/BonusEnum.h - ${MAIN_LIB_DIR}/bonuses/BonusList.h - ${MAIN_LIB_DIR}/bonuses/BonusParams.h - ${MAIN_LIB_DIR}/bonuses/BonusSelector.h - ${MAIN_LIB_DIR}/bonuses/BonusCustomTypes.h - ${MAIN_LIB_DIR}/bonuses/CBonusProxy.h - ${MAIN_LIB_DIR}/bonuses/CBonusSystemNode.h - ${MAIN_LIB_DIR}/bonuses/IBonusBearer.h - ${MAIN_LIB_DIR}/bonuses/Limiters.h - ${MAIN_LIB_DIR}/bonuses/Propagators.h - ${MAIN_LIB_DIR}/bonuses/Updaters.h - - ${MAIN_LIB_DIR}/campaign/CampaignConstants.h - ${MAIN_LIB_DIR}/campaign/CampaignHandler.h - ${MAIN_LIB_DIR}/campaign/CampaignScenarioPrologEpilog.h - ${MAIN_LIB_DIR}/campaign/CampaignState.h - - ${MAIN_LIB_DIR}/constants/EntityIdentifiers.h - ${MAIN_LIB_DIR}/constants/Enumerations.h - ${MAIN_LIB_DIR}/constants/IdentifierBase.h - ${MAIN_LIB_DIR}/constants/VariantIdentifier.h - ${MAIN_LIB_DIR}/constants/NumericConstants.h - ${MAIN_LIB_DIR}/constants/StringConstants.h - - ${MAIN_LIB_DIR}/events/ApplyDamage.h - ${MAIN_LIB_DIR}/events/GameResumed.h - ${MAIN_LIB_DIR}/events/ObjectVisitEnded.h - ${MAIN_LIB_DIR}/events/ObjectVisitStarted.h - ${MAIN_LIB_DIR}/events/PlayerGotTurn.h - ${MAIN_LIB_DIR}/events/TurnStarted.h - - ${MAIN_LIB_DIR}/filesystem/AdapterLoaders.h - ${MAIN_LIB_DIR}/filesystem/CArchiveLoader.h - ${MAIN_LIB_DIR}/filesystem/CBinaryReader.h - ${MAIN_LIB_DIR}/filesystem/CCompressedStream.h - ${MAIN_LIB_DIR}/filesystem/CFileInputStream.h - ${MAIN_LIB_DIR}/filesystem/CFilesystemLoader.h - ${MAIN_LIB_DIR}/filesystem/CInputOutputStream.h - ${MAIN_LIB_DIR}/filesystem/CInputStream.h - ${MAIN_LIB_DIR}/filesystem/CMemoryBuffer.h - ${MAIN_LIB_DIR}/filesystem/CMemoryStream.h - ${MAIN_LIB_DIR}/filesystem/COutputStream.h - ${MAIN_LIB_DIR}/filesystem/CStream.h - ${MAIN_LIB_DIR}/filesystem/CZipLoader.h - ${MAIN_LIB_DIR}/filesystem/CZipSaver.h - ${MAIN_LIB_DIR}/filesystem/FileInfo.h - ${MAIN_LIB_DIR}/filesystem/Filesystem.h - ${MAIN_LIB_DIR}/filesystem/ISimpleResourceLoader.h - ${MAIN_LIB_DIR}/filesystem/MinizipExtensions.h - ${MAIN_LIB_DIR}/filesystem/ResourcePath.h - - ${MAIN_LIB_DIR}/gameState/CGameState.h - ${MAIN_LIB_DIR}/gameState/CGameStateCampaign.h - ${MAIN_LIB_DIR}/gameState/EVictoryLossCheckResult.h - ${MAIN_LIB_DIR}/gameState/InfoAboutArmy.h - ${MAIN_LIB_DIR}/gameState/SThievesGuildInfo.h - ${MAIN_LIB_DIR}/gameState/TavernHeroesPool.h - ${MAIN_LIB_DIR}/gameState/TavernSlot.h - ${MAIN_LIB_DIR}/gameState/QuestInfo.h - - ${MAIN_LIB_DIR}/logging/CBasicLogConfigurator.h - ${MAIN_LIB_DIR}/logging/CLogger.h - - ${MAIN_LIB_DIR}/mapObjectConstructors/AObjectTypeHandler.h - ${MAIN_LIB_DIR}/mapObjectConstructors/CBankInstanceConstructor.h - ${MAIN_LIB_DIR}/mapObjectConstructors/CDefaultObjectTypeHandler.h - ${MAIN_LIB_DIR}/mapObjectConstructors/CObjectClassesHandler.h - ${MAIN_LIB_DIR}/mapObjectConstructors/CommonConstructors.h - ${MAIN_LIB_DIR}/mapObjectConstructors/CRewardableConstructor.h - ${MAIN_LIB_DIR}/mapObjectConstructors/DwellingInstanceConstructor.h - ${MAIN_LIB_DIR}/mapObjectConstructors/HillFortInstanceConstructor.h - ${MAIN_LIB_DIR}/mapObjectConstructors/IObjectInfo.h - ${MAIN_LIB_DIR}/mapObjectConstructors/RandomMapInfo.h - ${MAIN_LIB_DIR}/mapObjectConstructors/ShipyardInstanceConstructor.h - ${MAIN_LIB_DIR}/mapObjectConstructors/SObjectSounds.h - - ${MAIN_LIB_DIR}/mapObjects/CArmedInstance.h - ${MAIN_LIB_DIR}/mapObjects/CBank.h - ${MAIN_LIB_DIR}/mapObjects/CGCreature.h - ${MAIN_LIB_DIR}/mapObjects/CGDwelling.h - ${MAIN_LIB_DIR}/mapObjects/CGHeroInstance.h - ${MAIN_LIB_DIR}/mapObjects/CGMarket.h - ${MAIN_LIB_DIR}/mapObjects/CGObjectInstance.h - ${MAIN_LIB_DIR}/mapObjects/CGPandoraBox.h - ${MAIN_LIB_DIR}/mapObjects/CGTownBuilding.h - ${MAIN_LIB_DIR}/mapObjects/CGTownInstance.h - ${MAIN_LIB_DIR}/mapObjects/CObjectHandler.h - ${MAIN_LIB_DIR}/mapObjects/CQuest.h - ${MAIN_LIB_DIR}/mapObjects/CRewardableObject.h - ${MAIN_LIB_DIR}/mapObjects/IMarket.h - ${MAIN_LIB_DIR}/mapObjects/IObjectInterface.h - ${MAIN_LIB_DIR}/mapObjects/MapObjects.h - ${MAIN_LIB_DIR}/mapObjects/MiscObjects.h - ${MAIN_LIB_DIR}/mapObjects/ObjectTemplate.h - - ${MAIN_LIB_DIR}/mapping/CDrawRoadsOperation.h - ${MAIN_LIB_DIR}/mapping/CMapDefines.h - ${MAIN_LIB_DIR}/mapping/CMapEditManager.h - ${MAIN_LIB_DIR}/mapping/CMapHeader.h - ${MAIN_LIB_DIR}/mapping/CMap.h - ${MAIN_LIB_DIR}/mapping/CMapInfo.h - ${MAIN_LIB_DIR}/mapping/CMapOperation.h - ${MAIN_LIB_DIR}/mapping/CMapService.h - ${MAIN_LIB_DIR}/mapping/MapEditUtils.h - ${MAIN_LIB_DIR}/mapping/MapIdentifiersH3M.h - ${MAIN_LIB_DIR}/mapping/MapFeaturesH3M.h - ${MAIN_LIB_DIR}/mapping/MapFormatH3M.h - ${MAIN_LIB_DIR}/mapping/MapFormat.h - ${MAIN_LIB_DIR}/mapping/MapReaderH3M.h - ${MAIN_LIB_DIR}/mapping/MapFormatJson.h - ${MAIN_LIB_DIR}/mapping/ObstacleProxy.h - - ${MAIN_LIB_DIR}/modding/ActiveModsInSaveList.h - ${MAIN_LIB_DIR}/modding/CModHandler.h - ${MAIN_LIB_DIR}/modding/CModInfo.h - ${MAIN_LIB_DIR}/modding/CModVersion.h - ${MAIN_LIB_DIR}/modding/ContentTypeHandler.h - ${MAIN_LIB_DIR}/modding/IdentifierStorage.h - ${MAIN_LIB_DIR}/modding/ModIncompatibility.h - ${MAIN_LIB_DIR}/modding/ModScope.h - ${MAIN_LIB_DIR}/modding/ModUtility.h - ${MAIN_LIB_DIR}/modding/ModVerificationInfo.h - - ${MAIN_LIB_DIR}/network/NetworkConnection.h - ${MAIN_LIB_DIR}/network/NetworkDefines.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 - ${MAIN_LIB_DIR}/networkPacks/BattleChanges.h - ${MAIN_LIB_DIR}/networkPacks/Component.h - ${MAIN_LIB_DIR}/networkPacks/EInfoWindowMode.h - ${MAIN_LIB_DIR}/networkPacks/EntityChanges.h - ${MAIN_LIB_DIR}/networkPacks/EOpenWindowMode.h - ${MAIN_LIB_DIR}/networkPacks/NetPacksBase.h - ${MAIN_LIB_DIR}/networkPacks/NetPackVisitor.h - ${MAIN_LIB_DIR}/networkPacks/ObjProperty.h - ${MAIN_LIB_DIR}/networkPacks/PacksForClient.h - ${MAIN_LIB_DIR}/networkPacks/PacksForClientBattle.h - ${MAIN_LIB_DIR}/networkPacks/PacksForLobby.h - ${MAIN_LIB_DIR}/networkPacks/PacksForServer.h - ${MAIN_LIB_DIR}/networkPacks/SetStackEffect.h - ${MAIN_LIB_DIR}/networkPacks/StackLocation.h - ${MAIN_LIB_DIR}/networkPacks/TradeItem.h - - ${MAIN_LIB_DIR}/pathfinder/INodeStorage.h - ${MAIN_LIB_DIR}/pathfinder/CGPathNode.h - ${MAIN_LIB_DIR}/pathfinder/CPathfinder.h - ${MAIN_LIB_DIR}/pathfinder/NodeStorage.h - ${MAIN_LIB_DIR}/pathfinder/PathfinderOptions.h - ${MAIN_LIB_DIR}/pathfinder/PathfinderUtil.h - ${MAIN_LIB_DIR}/pathfinder/PathfindingRules.h - ${MAIN_LIB_DIR}/pathfinder/TurnInfo.h - - ${MAIN_LIB_DIR}/registerTypes/RegisterTypes.h - ${MAIN_LIB_DIR}/registerTypes/RegisterTypesClientPacks.h - ${MAIN_LIB_DIR}/registerTypes/RegisterTypesLobbyPacks.h - ${MAIN_LIB_DIR}/registerTypes/RegisterTypesMapObjects.h - ${MAIN_LIB_DIR}/registerTypes/RegisterTypesServerPacks.h - - ${MAIN_LIB_DIR}/rewardable/Configuration.h - ${MAIN_LIB_DIR}/rewardable/Info.h - ${MAIN_LIB_DIR}/rewardable/Interface.h - ${MAIN_LIB_DIR}/rewardable/Limiter.h - ${MAIN_LIB_DIR}/rewardable/Reward.h - - ${MAIN_LIB_DIR}/rmg/RmgArea.h - ${MAIN_LIB_DIR}/rmg/RmgObject.h - ${MAIN_LIB_DIR}/rmg/RmgPath.h - ${MAIN_LIB_DIR}/rmg/CMapGenerator.h - ${MAIN_LIB_DIR}/rmg/CMapGenOptions.h - ${MAIN_LIB_DIR}/rmg/CRmgTemplate.h - ${MAIN_LIB_DIR}/rmg/CRmgTemplateStorage.h - ${MAIN_LIB_DIR}/rmg/CZonePlacer.h - ${MAIN_LIB_DIR}/rmg/TileInfo.h - ${MAIN_LIB_DIR}/rmg/Zone.h - ${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 - ${MAIN_LIB_DIR}/rmg/modificators/RoadPlacer.h - ${MAIN_LIB_DIR}/rmg/modificators/TreasurePlacer.h - ${MAIN_LIB_DIR}/rmg/modificators/PrisonHeroPlacer.h - ${MAIN_LIB_DIR}/rmg/modificators/QuestArtifactPlacer.h - ${MAIN_LIB_DIR}/rmg/modificators/ConnectionsPlacer.h - ${MAIN_LIB_DIR}/rmg/modificators/WaterAdopter.h - ${MAIN_LIB_DIR}/rmg/modificators/MinePlacer.h - ${MAIN_LIB_DIR}/rmg/modificators/TownPlacer.h - ${MAIN_LIB_DIR}/rmg/modificators/WaterProxy.h - ${MAIN_LIB_DIR}/rmg/modificators/WaterRoutes.h - ${MAIN_LIB_DIR}/rmg/modificators/RockPlacer.h - ${MAIN_LIB_DIR}/rmg/modificators/RockFiller.h - ${MAIN_LIB_DIR}/rmg/modificators/ObstaclePlacer.h - ${MAIN_LIB_DIR}/rmg/modificators/RiverPlacer.h - ${MAIN_LIB_DIR}/rmg/modificators/TerrainPainter.h - ${MAIN_LIB_DIR}/rmg/threadpool/BlockingQueue.h - ${MAIN_LIB_DIR}/rmg/threadpool/ThreadPool.h - ${MAIN_LIB_DIR}/rmg/threadpool/MapProxy.h - - ${MAIN_LIB_DIR}/serializer/BinaryDeserializer.h - ${MAIN_LIB_DIR}/serializer/BinarySerializer.h - ${MAIN_LIB_DIR}/serializer/CLoadFile.h - ${MAIN_LIB_DIR}/serializer/CMemorySerializer.h - ${MAIN_LIB_DIR}/serializer/Connection.h - ${MAIN_LIB_DIR}/serializer/CSaveFile.h - ${MAIN_LIB_DIR}/serializer/CSerializer.h - ${MAIN_LIB_DIR}/serializer/CTypeList.h - ${MAIN_LIB_DIR}/serializer/JsonDeserializer.h - ${MAIN_LIB_DIR}/serializer/JsonSerializeFormat.h - ${MAIN_LIB_DIR}/serializer/JsonSerializer.h - ${MAIN_LIB_DIR}/serializer/JsonUpdater.h - ${MAIN_LIB_DIR}/serializer/Cast.h - ${MAIN_LIB_DIR}/serializer/ESerializationVersion.h - - ${MAIN_LIB_DIR}/spells/AbilityCaster.h - ${MAIN_LIB_DIR}/spells/AdventureSpellMechanics.h - ${MAIN_LIB_DIR}/spells/BattleSpellMechanics.h - ${MAIN_LIB_DIR}/spells/BonusCaster.h - ${MAIN_LIB_DIR}/spells/CSpellHandler.h - ${MAIN_LIB_DIR}/spells/ExternalCaster.h - ${MAIN_LIB_DIR}/spells/ISpellMechanics.h - ${MAIN_LIB_DIR}/spells/ObstacleCasterProxy.h - ${MAIN_LIB_DIR}/spells/Problem.h - ${MAIN_LIB_DIR}/spells/ProxyCaster.h - ${MAIN_LIB_DIR}/spells/TargetCondition.h - ${MAIN_LIB_DIR}/spells/ViewSpellInt.h - - ${MAIN_LIB_DIR}/spells/effects/Catapult.h - ${MAIN_LIB_DIR}/spells/effects/Clone.h - ${MAIN_LIB_DIR}/spells/effects/Damage.h - ${MAIN_LIB_DIR}/spells/effects/DemonSummon.h - ${MAIN_LIB_DIR}/spells/effects/Dispel.h - ${MAIN_LIB_DIR}/spells/effects/Effect.h - ${MAIN_LIB_DIR}/spells/effects/Effects.h - ${MAIN_LIB_DIR}/spells/effects/EffectsFwd.h - ${MAIN_LIB_DIR}/spells/effects/Heal.h - ${MAIN_LIB_DIR}/spells/effects/LocationEffect.h - ${MAIN_LIB_DIR}/spells/effects/Obstacle.h - ${MAIN_LIB_DIR}/spells/effects/Registry.h - ${MAIN_LIB_DIR}/spells/effects/UnitEffect.h - ${MAIN_LIB_DIR}/spells/effects/Summon.h - ${MAIN_LIB_DIR}/spells/effects/Teleport.h - ${MAIN_LIB_DIR}/spells/effects/Timed.h - ${MAIN_LIB_DIR}/spells/effects/RemoveObstacle.h - ${MAIN_LIB_DIR}/spells/effects/Sacrifice.h - - ${MAIN_LIB_DIR}/AI_Base.h - ${MAIN_LIB_DIR}/ArtifactUtils.h - ${MAIN_LIB_DIR}/BattleFieldHandler.h - ${MAIN_LIB_DIR}/CAndroidVMHelper.h - ${MAIN_LIB_DIR}/CArtHandler.h - ${MAIN_LIB_DIR}/CArtifactInstance.h - ${MAIN_LIB_DIR}/CBonusTypeHandler.h - ${MAIN_LIB_DIR}/CBuildingHandler.h - ${MAIN_LIB_DIR}/CConfigHandler.h - ${MAIN_LIB_DIR}/CConsoleHandler.h - ${MAIN_LIB_DIR}/CCreatureHandler.h - ${MAIN_LIB_DIR}/CCreatureSet.h - ${MAIN_LIB_DIR}/CGameInfoCallback.h - ${MAIN_LIB_DIR}/CGameInterface.h - ${MAIN_LIB_DIR}/CGeneralTextHandler.h - ${MAIN_LIB_DIR}/CHeroHandler.h - ${MAIN_LIB_DIR}/CondSh.h - ${MAIN_LIB_DIR}/ConstTransitivePtr.h - ${MAIN_LIB_DIR}/Color.h - ${MAIN_LIB_DIR}/CPlayerState.h - ${MAIN_LIB_DIR}/CRandomGenerator.h - ${MAIN_LIB_DIR}/CScriptingModule.h - ${MAIN_LIB_DIR}/CSkillHandler.h - ${MAIN_LIB_DIR}/CSoundBase.h - ${MAIN_LIB_DIR}/CStack.h - ${MAIN_LIB_DIR}/CStopWatch.h - ${MAIN_LIB_DIR}/CThreadHelper.h - ${MAIN_LIB_DIR}/CTownHandler.h - ${MAIN_LIB_DIR}/ExtraOptionsInfo.h - ${MAIN_LIB_DIR}/FunctionList.h - ${MAIN_LIB_DIR}/GameCallbackHolder.h - ${MAIN_LIB_DIR}/GameConstants.h - ${MAIN_LIB_DIR}/GameSettings.h - ${MAIN_LIB_DIR}/IBonusTypeHandler.h - ${MAIN_LIB_DIR}/IGameCallback.h - ${MAIN_LIB_DIR}/IGameEventsReceiver.h - ${MAIN_LIB_DIR}/IHandlerBase.h - ${MAIN_LIB_DIR}/int3.h - ${MAIN_LIB_DIR}/JsonDetail.h - ${MAIN_LIB_DIR}/JsonNode.h - ${MAIN_LIB_DIR}/JsonRandom.h - ${MAIN_LIB_DIR}/Languages.h - ${MAIN_LIB_DIR}/LoadProgress.h - ${MAIN_LIB_DIR}/LogicalExpression.h - ${MAIN_LIB_DIR}/MetaString.h - ${MAIN_LIB_DIR}/ObstacleHandler.h - ${MAIN_LIB_DIR}/Point.h - ${MAIN_LIB_DIR}/Rect.h - ${MAIN_LIB_DIR}/Rect.cpp - ${MAIN_LIB_DIR}/ResourceSet.h - ${MAIN_LIB_DIR}/RiverHandler.h - ${MAIN_LIB_DIR}/RoadHandler.h - ${MAIN_LIB_DIR}/ScriptHandler.h - ${MAIN_LIB_DIR}/ScopeGuard.h - ${MAIN_LIB_DIR}/StartInfo.h - ${MAIN_LIB_DIR}/TerrainHandler.h - ${MAIN_LIB_DIR}/TextOperations.h - ${MAIN_LIB_DIR}/TurnTimerInfo.h - ${MAIN_LIB_DIR}/UnlockGuard.h - ${MAIN_LIB_DIR}/VCMIDirs.h - ${MAIN_LIB_DIR}/vcmi_endian.h - ${MAIN_LIB_DIR}/VCMI_Lib.h - ) - - assign_source_group(${lib_SRCS} ${lib_HEADERS}) - - add_library(${TARGET_NAME} ${LIBRARY_TYPE} ${lib_SRCS} ${lib_HEADERS}) - set_target_properties(${TARGET_NAME} PROPERTIES COMPILE_DEFINITIONS "VCMI_DLL=1") - target_link_libraries(${TARGET_NAME} PUBLIC - minizip::minizip ZLIB::ZLIB - ${SYSTEM_LIBS} Boost::boost Boost::thread Boost::filesystem Boost::program_options Boost::locale Boost::date_time - ) - if(APPLE_IOS) - target_link_libraries(${TARGET_NAME} PUBLIC iOS_utils) - endif() - - target_include_directories(${TARGET_NAME} - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} - PUBLIC ${MAIN_LIB_DIR}/.. - PUBLIC ${MAIN_LIB_DIR}/../include - PUBLIC ${MAIN_LIB_DIR} - ) - - if(WIN32) - set_target_properties(${TARGET_NAME} - PROPERTIES - OUTPUT_NAME "VCMI_lib" - PROJECT_LABEL "VCMI_lib" - ) - endif() - - vcmi_set_output_dir(${TARGET_NAME} "") - - enable_pch(${TARGET_NAME}) - - # We want to deploy assets into build directory for easier debugging without install - if(COPY_CONFIG_ON_BUILD) - add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config - COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ${MAIN_LIB_DIR}/../config ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ${MAIN_LIB_DIR}/../Mods ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods - ) - endif() - - # Update version before vcmi compiling - if(TARGET update_version) - add_dependencies(${TARGET_NAME} update_version) - endif() - - if("${LIBRARY_TYPE}" STREQUAL SHARED) - install(TARGETS ${TARGET_NAME} RUNTIME DESTINATION ${LIB_DIR} LIBRARY DESTINATION ${LIB_DIR}) - endif() - if(APPLE_IOS AND NOT USING_CONAN) - get_target_property(LINKED_LIBS ${TARGET_NAME} LINK_LIBRARIES) - foreach(LINKED_LIB IN LISTS LINKED_LIBS) - if(NOT TARGET ${LINKED_LIB}) - if(LINKED_LIB MATCHES "\\${CMAKE_SHARED_LIBRARY_SUFFIX}$") - install(FILES ${LINKED_LIB} DESTINATION ${LIB_DIR}) - endif() - continue() - endif() - - get_target_property(LIB_TYPE ${LINKED_LIB} TYPE) - if(NOT LIB_TYPE STREQUAL "SHARED_LIBRARY") - continue() - endif() - - get_target_property(_aliased ${LINKED_LIB} ALIASED_TARGET) - if(_aliased) - set(LINKED_LIB_REAL ${_aliased}) - else() - set(LINKED_LIB_REAL ${LINKED_LIB}) - endif() - - get_target_property(_imported ${LINKED_LIB_REAL} IMPORTED) - if(_imported) - set(INSTALL_TYPE IMPORTED_RUNTIME_ARTIFACTS) - get_target_property(BOOST_DEPENDENCIES ${LINKED_LIB_REAL} INTERFACE_LINK_LIBRARIES) - foreach(BOOST_DEPENDENCY IN LISTS BOOST_DEPENDENCIES) - get_target_property(BOOST_DEPENDENCY_TYPE ${BOOST_DEPENDENCY} TYPE) - if(BOOST_DEPENDENCY_TYPE STREQUAL "SHARED_LIBRARY") - install(IMPORTED_RUNTIME_ARTIFACTS ${BOOST_DEPENDENCY} LIBRARY DESTINATION ${LIB_DIR}) - endif() - endforeach() - else() - set(INSTALL_TYPE TARGETS) - endif() - install(${INSTALL_TYPE} ${LINKED_LIB_REAL} LIBRARY DESTINATION ${LIB_DIR}) - endforeach() - endif() -endmacro() diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index f55f8d32c..9622ff6d2 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -135,7 +135,7 @@ if (NOT APPLE_IOS AND NOT ANDROID) target_link_libraries(vcmilauncher SDL2::SDL2) endif() -target_link_libraries(vcmilauncher ${VCMI_LIB_TARGET} Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) +target_link_libraries(vcmilauncher vcmi Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) target_include_directories(vcmilauncher PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 452d8556c..1d0a13f4f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,22 +1,775 @@ +set(lib_SRCS + StdInc.cpp + + battle/AccessibilityInfo.cpp + battle/BattleAction.cpp + battle/BattleAttackInfo.cpp + battle/BattleHex.cpp + battle/BattleInfo.cpp + battle/BattleProxy.cpp + battle/BattleStateInfoForRetreat.cpp + battle/CBattleInfoCallback.cpp + battle/CBattleInfoEssentials.cpp + battle/CObstacleInstance.cpp + battle/CPlayerBattleCallback.cpp + battle/CUnitState.cpp + battle/DamageCalculator.cpp + battle/Destination.cpp + battle/IBattleState.cpp + battle/ReachabilityInfo.cpp + battle/SideInBattle.cpp + battle/SiegeInfo.cpp + battle/Unit.cpp + + bonuses/Bonus.cpp + bonuses/BonusEnum.cpp + bonuses/BonusList.cpp + bonuses/BonusParams.cpp + bonuses/BonusSelector.cpp + bonuses/BonusCustomTypes.cpp + bonuses/CBonusProxy.cpp + bonuses/CBonusSystemNode.cpp + bonuses/IBonusBearer.cpp + bonuses/Limiters.cpp + bonuses/Propagators.cpp + bonuses/Updaters.cpp + + campaign/CampaignHandler.cpp + campaign/CampaignState.cpp + + constants/EntityIdentifiers.cpp + + events/ApplyDamage.cpp + events/GameResumed.cpp + events/ObjectVisitEnded.cpp + events/ObjectVisitStarted.cpp + events/PlayerGotTurn.cpp + events/TurnStarted.cpp + + filesystem/AdapterLoaders.cpp + filesystem/CArchiveLoader.cpp + filesystem/CBinaryReader.cpp + filesystem/CCompressedStream.cpp + filesystem/CFileInputStream.cpp + filesystem/CFilesystemLoader.cpp + filesystem/CMemoryBuffer.cpp + filesystem/CMemoryStream.cpp + filesystem/CZipLoader.cpp + filesystem/CZipSaver.cpp + filesystem/FileInfo.cpp + filesystem/Filesystem.cpp + filesystem/MinizipExtensions.cpp + filesystem/ResourcePath.cpp + + gameState/CGameState.cpp + gameState/CGameStateCampaign.cpp + gameState/InfoAboutArmy.cpp + gameState/TavernHeroesPool.cpp + + logging/CBasicLogConfigurator.cpp + logging/CLogger.cpp + + mapObjectConstructors/AObjectTypeHandler.cpp + mapObjectConstructors/CBankInstanceConstructor.cpp + mapObjectConstructors/CObjectClassesHandler.cpp + mapObjectConstructors/CommonConstructors.cpp + mapObjectConstructors/CRewardableConstructor.cpp + mapObjectConstructors/DwellingInstanceConstructor.cpp + mapObjectConstructors/HillFortInstanceConstructor.cpp + mapObjectConstructors/ShipyardInstanceConstructor.cpp + + mapObjects/CArmedInstance.cpp + mapObjects/CBank.cpp + mapObjects/CGCreature.cpp + mapObjects/CGDwelling.cpp + mapObjects/CGHeroInstance.cpp + mapObjects/CGMarket.cpp + mapObjects/CGObjectInstance.cpp + mapObjects/CGPandoraBox.cpp + mapObjects/CGTownBuilding.cpp + mapObjects/CGTownInstance.cpp + mapObjects/CObjectHandler.cpp + mapObjects/CQuest.cpp + mapObjects/CRewardableObject.cpp + mapObjects/IMarket.cpp + mapObjects/IObjectInterface.cpp + mapObjects/MiscObjects.cpp + mapObjects/ObjectTemplate.cpp + + mapping/CDrawRoadsOperation.cpp + mapping/CMap.cpp + mapping/CMapHeader.cpp + mapping/CMapEditManager.cpp + mapping/CMapInfo.cpp + mapping/CMapOperation.cpp + mapping/CMapService.cpp + mapping/MapEditUtils.cpp + mapping/MapIdentifiersH3M.cpp + mapping/MapFeaturesH3M.cpp + mapping/MapFormatH3M.cpp + mapping/MapReaderH3M.cpp + mapping/MapFormatJson.cpp + mapping/ObstacleProxy.cpp + + modding/ActiveModsInSaveList.cpp + modding/CModHandler.cpp + modding/CModInfo.cpp + modding/CModVersion.cpp + modding/ContentTypeHandler.cpp + modding/IdentifierStorage.cpp + modding/ModUtility.cpp + + network/NetworkConnection.cpp + network/NetworkHandler.cpp + network/NetworkServer.cpp + + networkPacks/NetPacksLib.cpp + + pathfinder/CGPathNode.cpp + pathfinder/CPathfinder.cpp + pathfinder/NodeStorage.cpp + pathfinder/PathfinderOptions.cpp + pathfinder/PathfindingRules.cpp + pathfinder/TurnInfo.cpp + + rewardable/Configuration.cpp + rewardable/Info.cpp + rewardable/Interface.cpp + rewardable/Limiter.cpp + rewardable/Reward.cpp + + rmg/RmgArea.cpp + rmg/RmgObject.cpp + rmg/RmgPath.cpp + rmg/CMapGenerator.cpp + rmg/CMapGenOptions.cpp + rmg/CRmgTemplate.cpp + rmg/CRmgTemplateStorage.cpp + rmg/CZonePlacer.cpp + rmg/TileInfo.cpp + rmg/Zone.cpp + rmg/Functions.cpp + rmg/RmgMap.cpp + rmg/PenroseTiling.cpp + rmg/modificators/Modificator.cpp + rmg/modificators/ObjectManager.cpp + rmg/modificators/ObjectDistributor.cpp + rmg/modificators/RoadPlacer.cpp + rmg/modificators/TreasurePlacer.cpp + rmg/modificators/PrisonHeroPlacer.cpp + rmg/modificators/QuestArtifactPlacer.cpp + rmg/modificators/ConnectionsPlacer.cpp + rmg/modificators/WaterAdopter.cpp + rmg/modificators/MinePlacer.cpp + rmg/modificators/TownPlacer.cpp + rmg/modificators/WaterProxy.cpp + rmg/modificators/WaterRoutes.cpp + rmg/modificators/RockPlacer.cpp + rmg/modificators/RockFiller.cpp + rmg/modificators/ObstaclePlacer.cpp + rmg/modificators/RiverPlacer.cpp + rmg/modificators/TerrainPainter.cpp + rmg/threadpool/MapProxy.cpp + + serializer/BinaryDeserializer.cpp + serializer/BinarySerializer.cpp + serializer/CLoadFile.cpp + serializer/CMemorySerializer.cpp + serializer/Connection.cpp + serializer/CSaveFile.cpp + serializer/CSerializer.cpp + serializer/CTypeList.cpp + serializer/JsonDeserializer.cpp + serializer/JsonSerializeFormat.cpp + serializer/JsonSerializer.cpp + serializer/JsonUpdater.cpp + + spells/AbilityCaster.cpp + spells/AdventureSpellMechanics.cpp + spells/BattleSpellMechanics.cpp + spells/BonusCaster.cpp + spells/CSpellHandler.cpp + spells/ExternalCaster.cpp + spells/ISpellMechanics.cpp + spells/ObstacleCasterProxy.cpp + spells/Problem.cpp + spells/ProxyCaster.cpp + spells/TargetCondition.cpp + spells/ViewSpellInt.cpp + + spells/effects/Catapult.cpp + spells/effects/Clone.cpp + spells/effects/Damage.cpp + spells/effects/DemonSummon.cpp + spells/effects/Dispel.cpp + spells/effects/Effect.cpp + spells/effects/Effects.cpp + spells/effects/Heal.cpp + spells/effects/LocationEffect.cpp + spells/effects/Moat.cpp + spells/effects/Obstacle.cpp + spells/effects/Registry.cpp + spells/effects/UnitEffect.cpp + spells/effects/Summon.cpp + spells/effects/Teleport.cpp + spells/effects/Timed.cpp + spells/effects/RemoveObstacle.cpp + spells/effects/Sacrifice.cpp + + vstd/DateUtils.cpp + vstd/StringUtils.cpp + + ArtifactUtils.cpp + BasicTypes.cpp + BattleFieldHandler.cpp + CAndroidVMHelper.cpp + CArtHandler.cpp + CArtifactInstance.cpp + CBonusTypeHandler.cpp + CBuildingHandler.cpp + CConfigHandler.cpp + CConsoleHandler.cpp + CCreatureHandler.cpp + CCreatureSet.cpp + CGameInfoCallback.cpp + CGameInterface.cpp + CGeneralTextHandler.cpp + CHeroHandler.cpp + CPlayerState.cpp + CRandomGenerator.cpp + CScriptingModule.cpp + CSkillHandler.cpp + CStack.cpp + CThreadHelper.cpp + CTownHandler.cpp + GameSettings.cpp + IGameCallback.cpp + IHandlerBase.cpp + JsonDetail.cpp + JsonNode.cpp + JsonRandom.cpp + LoadProgress.cpp + LogicalExpression.cpp + MetaString.cpp + ObstacleHandler.cpp + StartInfo.cpp + ResourceSet.cpp + RiverHandler.cpp + RoadHandler.cpp + ScriptHandler.cpp + TerrainHandler.cpp + TextOperations.cpp + TurnTimerInfo.cpp + VCMIDirs.cpp + VCMI_Lib.cpp +) + +# Version.cpp is a generated file +if(ENABLE_GITVERSION) + list(APPEND lib_SRCS ${CMAKE_BINARY_DIR}/Version.cpp) + set_source_files_properties(${CMAKE_BINARY_DIR}/Version.cpp + PROPERTIES GENERATED TRUE + ) +endif() + +set(lib_HEADERS + ../include/vstd/CLoggerBase.h + ../Global.h + ../AUTHORS.h + StdInc.h + + ../include/vstd/ContainerUtils.h + ../include/vstd/RNG.h + ../include/vstd/DateUtils.h + ../include/vstd/StringUtils.h + + ../include/vcmi/events/AdventureEvents.h + ../include/vcmi/events/ApplyDamage.h + ../include/vcmi/events/BattleEvents.h + ../include/vcmi/events/Event.h + ../include/vcmi/events/EventBus.h + ../include/vcmi/events/GameResumed.h + ../include/vcmi/events/GenericEvents.h + ../include/vcmi/events/ObjectVisitEnded.h + ../include/vcmi/events/ObjectVisitStarted.h + ../include/vcmi/events/PlayerGotTurn.h + ../include/vcmi/events/SubscriptionRegistry.h + ../include/vcmi/events/TurnStarted.h + + ../include/vcmi/scripting/Service.h + + ../include/vcmi/spells/Caster.h + ../include/vcmi/spells/Magic.h + ../include/vcmi/spells/Service.h + ../include/vcmi/spells/Spell.h + + ../include/vcmi/Artifact.h + ../include/vcmi/ArtifactService.h + ../include/vcmi/Creature.h + ../include/vcmi/CreatureService.h + ../include/vcmi/Entity.h + ../include/vcmi/Environment.h + ../include/vcmi/Faction.h + ../include/vcmi/FactionService.h + ../include/vcmi/HeroClass.h + ../include/vcmi/HeroClassService.h + ../include/vcmi/HeroType.h + ../include/vcmi/HeroTypeService.h + ../include/vcmi/Metatype.h + ../include/vcmi/Player.h + ../include/vcmi/ServerCallback.h + ../include/vcmi/Services.h + ../include/vcmi/Skill.h + ../include/vcmi/SkillService.h + ../include/vcmi/Team.h + + battle/AccessibilityInfo.h + battle/AutocombatPreferences.h + battle/BattleAction.h + battle/BattleAttackInfo.h + battle/BattleHex.h + battle/BattleInfo.h + battle/BattleStateInfoForRetreat.h + battle/BattleProxy.h + battle/CBattleInfoCallback.h + battle/CBattleInfoEssentials.h + battle/CObstacleInstance.h + battle/CPlayerBattleCallback.h + battle/CUnitState.h + battle/DamageCalculator.h + battle/Destination.h + battle/IBattleInfoCallback.h + battle/IBattleState.h + battle/IUnitInfo.h + battle/PossiblePlayerBattleAction.h + battle/ReachabilityInfo.h + battle/SideInBattle.h + battle/SiegeInfo.h + battle/Unit.h + + bonuses/Bonus.h + bonuses/BonusEnum.h + bonuses/BonusList.h + bonuses/BonusParams.h + bonuses/BonusSelector.h + bonuses/BonusCustomTypes.h + bonuses/CBonusProxy.h + bonuses/CBonusSystemNode.h + bonuses/IBonusBearer.h + bonuses/Limiters.h + bonuses/Propagators.h + bonuses/Updaters.h + + campaign/CampaignConstants.h + campaign/CampaignHandler.h + campaign/CampaignScenarioPrologEpilog.h + campaign/CampaignState.h + + constants/EntityIdentifiers.h + constants/Enumerations.h + constants/IdentifierBase.h + constants/VariantIdentifier.h + constants/NumericConstants.h + constants/StringConstants.h + + events/ApplyDamage.h + events/GameResumed.h + events/ObjectVisitEnded.h + events/ObjectVisitStarted.h + events/PlayerGotTurn.h + events/TurnStarted.h + + filesystem/AdapterLoaders.h + filesystem/CArchiveLoader.h + filesystem/CBinaryReader.h + filesystem/CCompressedStream.h + filesystem/CFileInputStream.h + filesystem/CFilesystemLoader.h + filesystem/CInputOutputStream.h + filesystem/CInputStream.h + filesystem/CMemoryBuffer.h + filesystem/CMemoryStream.h + filesystem/COutputStream.h + filesystem/CStream.h + filesystem/CZipLoader.h + filesystem/CZipSaver.h + filesystem/FileInfo.h + filesystem/Filesystem.h + filesystem/ISimpleResourceLoader.h + filesystem/MinizipExtensions.h + filesystem/ResourcePath.h + + gameState/CGameState.h + gameState/CGameStateCampaign.h + gameState/EVictoryLossCheckResult.h + gameState/InfoAboutArmy.h + gameState/SThievesGuildInfo.h + gameState/TavernHeroesPool.h + gameState/TavernSlot.h + gameState/QuestInfo.h + + logging/CBasicLogConfigurator.h + logging/CLogger.h + + mapObjectConstructors/AObjectTypeHandler.h + mapObjectConstructors/CBankInstanceConstructor.h + mapObjectConstructors/CDefaultObjectTypeHandler.h + mapObjectConstructors/CObjectClassesHandler.h + mapObjectConstructors/CommonConstructors.h + mapObjectConstructors/CRewardableConstructor.h + mapObjectConstructors/DwellingInstanceConstructor.h + mapObjectConstructors/HillFortInstanceConstructor.h + mapObjectConstructors/IObjectInfo.h + mapObjectConstructors/RandomMapInfo.h + mapObjectConstructors/ShipyardInstanceConstructor.h + mapObjectConstructors/SObjectSounds.h + + mapObjects/CArmedInstance.h + mapObjects/CBank.h + mapObjects/CGCreature.h + mapObjects/CGDwelling.h + mapObjects/CGHeroInstance.h + mapObjects/CGMarket.h + mapObjects/CGObjectInstance.h + mapObjects/CGPandoraBox.h + mapObjects/CGTownBuilding.h + mapObjects/CGTownInstance.h + mapObjects/CObjectHandler.h + mapObjects/CQuest.h + mapObjects/CRewardableObject.h + mapObjects/IMarket.h + mapObjects/IObjectInterface.h + mapObjects/MapObjects.h + mapObjects/MiscObjects.h + mapObjects/ObjectTemplate.h + + mapping/CDrawRoadsOperation.h + mapping/CMapDefines.h + mapping/CMapEditManager.h + mapping/CMapHeader.h + mapping/CMap.h + mapping/CMapInfo.h + mapping/CMapOperation.h + mapping/CMapService.h + mapping/MapEditUtils.h + mapping/MapIdentifiersH3M.h + mapping/MapFeaturesH3M.h + mapping/MapFormatH3M.h + mapping/MapFormat.h + mapping/MapReaderH3M.h + mapping/MapFormatJson.h + mapping/ObstacleProxy.h + + modding/ActiveModsInSaveList.h + modding/CModHandler.h + modding/CModInfo.h + modding/CModVersion.h + modding/ContentTypeHandler.h + modding/IdentifierStorage.h + modding/ModIncompatibility.h + modding/ModScope.h + modding/ModUtility.h + modding/ModVerificationInfo.h + + network/NetworkConnection.h + network/NetworkDefines.h + network/NetworkHandler.h + network/NetworkInterface.h + network/NetworkServer.h + + networkPacks/ArtifactLocation.h + networkPacks/BattleChanges.h + networkPacks/Component.h + networkPacks/EInfoWindowMode.h + networkPacks/EntityChanges.h + networkPacks/EOpenWindowMode.h + networkPacks/NetPacksBase.h + networkPacks/NetPackVisitor.h + networkPacks/ObjProperty.h + networkPacks/PacksForClient.h + networkPacks/PacksForClientBattle.h + networkPacks/PacksForLobby.h + networkPacks/PacksForServer.h + networkPacks/SetStackEffect.h + networkPacks/StackLocation.h + networkPacks/TradeItem.h + + pathfinder/INodeStorage.h + pathfinder/CGPathNode.h + pathfinder/CPathfinder.h + pathfinder/NodeStorage.h + pathfinder/PathfinderOptions.h + pathfinder/PathfinderUtil.h + pathfinder/PathfindingRules.h + pathfinder/TurnInfo.h + + registerTypes/RegisterTypes.h + registerTypes/RegisterTypesClientPacks.h + registerTypes/RegisterTypesLobbyPacks.h + registerTypes/RegisterTypesMapObjects.h + registerTypes/RegisterTypesServerPacks.h + + rewardable/Configuration.h + rewardable/Info.h + rewardable/Interface.h + rewardable/Limiter.h + rewardable/Reward.h + + rmg/RmgArea.h + rmg/RmgObject.h + rmg/RmgPath.h + rmg/CMapGenerator.h + rmg/CMapGenOptions.h + rmg/CRmgTemplate.h + rmg/CRmgTemplateStorage.h + rmg/CZonePlacer.h + rmg/TileInfo.h + rmg/Zone.h + rmg/RmgMap.h + rmg/float3.h + rmg/Functions.h + rmg/PenroseTiling.h + rmg/modificators/Modificator.h + rmg/modificators/ObjectManager.h + rmg/modificators/ObjectDistributor.h + rmg/modificators/RoadPlacer.h + rmg/modificators/TreasurePlacer.h + rmg/modificators/PrisonHeroPlacer.h + rmg/modificators/QuestArtifactPlacer.h + rmg/modificators/ConnectionsPlacer.h + rmg/modificators/WaterAdopter.h + rmg/modificators/MinePlacer.h + rmg/modificators/TownPlacer.h + rmg/modificators/WaterProxy.h + rmg/modificators/WaterRoutes.h + rmg/modificators/RockPlacer.h + rmg/modificators/RockFiller.h + rmg/modificators/ObstaclePlacer.h + rmg/modificators/RiverPlacer.h + rmg/modificators/TerrainPainter.h + rmg/threadpool/BlockingQueue.h + rmg/threadpool/ThreadPool.h + rmg/threadpool/MapProxy.h + + serializer/BinaryDeserializer.h + serializer/BinarySerializer.h + serializer/CLoadFile.h + serializer/CMemorySerializer.h + serializer/Connection.h + serializer/CSaveFile.h + serializer/CSerializer.h + serializer/CTypeList.h + serializer/JsonDeserializer.h + serializer/JsonSerializeFormat.h + serializer/JsonSerializer.h + serializer/JsonUpdater.h + serializer/Cast.h + serializer/ESerializationVersion.h + + spells/AbilityCaster.h + spells/AdventureSpellMechanics.h + spells/BattleSpellMechanics.h + spells/BonusCaster.h + spells/CSpellHandler.h + spells/ExternalCaster.h + spells/ISpellMechanics.h + spells/ObstacleCasterProxy.h + spells/Problem.h + spells/ProxyCaster.h + spells/TargetCondition.h + spells/ViewSpellInt.h + + spells/effects/Catapult.h + spells/effects/Clone.h + spells/effects/Damage.h + spells/effects/DemonSummon.h + spells/effects/Dispel.h + spells/effects/Effect.h + spells/effects/Effects.h + spells/effects/EffectsFwd.h + spells/effects/Heal.h + spells/effects/LocationEffect.h + spells/effects/Obstacle.h + spells/effects/Registry.h + spells/effects/UnitEffect.h + spells/effects/Summon.h + spells/effects/Teleport.h + spells/effects/Timed.h + spells/effects/RemoveObstacle.h + spells/effects/Sacrifice.h + + AI_Base.h + ArtifactUtils.h + BattleFieldHandler.h + CAndroidVMHelper.h + CArtHandler.h + CArtifactInstance.h + CBonusTypeHandler.h + CBuildingHandler.h + CConfigHandler.h + CConsoleHandler.h + CCreatureHandler.h + CCreatureSet.h + CGameInfoCallback.h + CGameInterface.h + CGeneralTextHandler.h + CHeroHandler.h + CondSh.h + ConstTransitivePtr.h + Color.h + CPlayerState.h + CRandomGenerator.h + CScriptingModule.h + CSkillHandler.h + CSoundBase.h + CStack.h + CStopWatch.h + CThreadHelper.h + CTownHandler.h + ExtraOptionsInfo.h + FunctionList.h + GameCallbackHolder.h + GameConstants.h + GameSettings.h + IBonusTypeHandler.h + IGameCallback.h + IGameEventsReceiver.h + IHandlerBase.h + int3.h + JsonDetail.h + JsonNode.h + JsonRandom.h + Languages.h + LoadProgress.h + LogicalExpression.h + MetaString.h + ObstacleHandler.h + Point.h + Rect.h + Rect.cpp + ResourceSet.h + RiverHandler.h + RoadHandler.h + ScriptHandler.h + ScopeGuard.h + StartInfo.h + TerrainHandler.h + TextOperations.h + TurnTimerInfo.h + UnlockGuard.h + VCMIDirs.h + vcmi_endian.h + VCMI_Lib.h +) + +assign_source_group(${lib_SRCS} ${lib_HEADERS}) + if(ENABLE_STATIC_LIBS) - add_main_lib(${VCMI_LIB_TARGET} STATIC) - target_compile_definitions(${VCMI_LIB_TARGET} PRIVATE STATIC_AI) - target_link_libraries(${VCMI_LIB_TARGET} PRIVATE + add_library(vcmi STATIC ${lib_SRCS} ${lib_HEADERS}) +else() + add_library(vcmi SHARED ${lib_SRCS} ${lib_HEADERS}) +endif() +set_target_properties(vcmi PROPERTIES COMPILE_DEFINITIONS "VCMI_DLL=1") +target_link_libraries(vcmi PUBLIC + minizip::minizip ZLIB::ZLIB + ${SYSTEM_LIBS} Boost::boost Boost::thread Boost::filesystem Boost::program_options Boost::locale Boost::date_time +) + +if(ENABLE_STATIC_LIBS) + target_compile_definitions(vcmi PRIVATE STATIC_AI) + target_link_libraries(vcmi PRIVATE BattleAI EmptyAI StupidAI VCAI ) if(ENABLE_NULLKILLER_AI) - target_link_libraries(${VCMI_LIB_TARGET} PRIVATE Nullkiller) + target_link_libraries(vcmi PRIVATE Nullkiller) endif() -else() - add_main_lib(${VCMI_LIB_TARGET} SHARED) endif() # no longer necessary, but might be useful to keep in future # unfortunately at the moment tests do not support namespaced build, so enable only on some systems if(APPLE_IOS OR ANDROID) - target_compile_definitions(${VCMI_LIB_TARGET} PUBLIC VCMI_LIB_NAMESPACE=VCMI) + target_compile_definitions(vcmi PUBLIC VCMI_LIB_NAMESPACE=VCMI) endif() +if(APPLE_IOS) + target_link_libraries(vcmi PUBLIC iOS_utils) +endif() + +target_include_directories(vcmi + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + PUBLIC ${CMAKE_SOURCE_DIR} + PUBLIC ${CMAKE_SOURCE_DIR}/include +) + +if(WIN32) + set_target_properties(vcmi + PROPERTIES + OUTPUT_NAME "VCMI_lib" + PROJECT_LABEL "VCMI_lib" + ) +endif() + +vcmi_set_output_dir(vcmi "") + +enable_pch(vcmi) + +# We want to deploy assets into build directory for easier debugging without install +if(COPY_CONFIG_ON_BUILD) + add_custom_command(TARGET vcmi POST_BUILD + COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config + COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ../config ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ../Mods ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods + ) +endif() + +# Update version before vcmi compiling +if(TARGET update_version) + add_dependencies(vcmi update_version) +endif() + +if(NOT ENABLE_STATIC_LIBS) + install(TARGETS vcmi RUNTIME DESTINATION ${LIB_DIR} LIBRARY DESTINATION ${LIB_DIR}) +endif() + +if(APPLE_IOS AND NOT USING_CONAN) + get_target_property(LINKED_LIBS vcmi LINK_LIBRARIES) + foreach(LINKED_LIB IN LISTS LINKED_LIBS) + if(NOT TARGET ${LINKED_LIB}) + if(LINKED_LIB MATCHES "\\${CMAKE_SHARED_LIBRARY_SUFFIX}$") + install(FILES ${LINKED_LIB} DESTINATION ${LIB_DIR}) + endif() + continue() + endif() + + get_target_property(LIB_TYPE ${LINKED_LIB} TYPE) + if(NOT LIB_TYPE STREQUAL "SHARED_LIBRARY") + continue() + endif() + + get_target_property(_aliased ${LINKED_LIB} ALIASED_TARGET) + if(_aliased) + set(LINKED_LIB_REAL ${_aliased}) + else() + set(LINKED_LIB_REAL ${LINKED_LIB}) + endif() + + get_target_property(_imported ${LINKED_LIB_REAL} IMPORTED) + if(_imported) + set(INSTALL_TYPE IMPORTED_RUNTIME_ARTIFACTS) + get_target_property(BOOST_DEPENDENCIES ${LINKED_LIB_REAL} INTERFACE_LINK_LIBRARIES) + foreach(BOOST_DEPENDENCY IN LISTS BOOST_DEPENDENCIES) + get_target_property(BOOST_DEPENDENCY_TYPE ${BOOST_DEPENDENCY} TYPE) + if(BOOST_DEPENDENCY_TYPE STREQUAL "SHARED_LIBRARY") + install(IMPORTED_RUNTIME_ARTIFACTS ${BOOST_DEPENDENCY} LIBRARY DESTINATION ${LIB_DIR}) + endif() + endforeach() + else() + set(INSTALL_TYPE TARGETS) + endif() + install(${INSTALL_TYPE} ${LINKED_LIB_REAL} LIBRARY DESTINATION ${LIB_DIR}) + endforeach() +endif() \ No newline at end of file diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index 0a1ff22bc..629534604 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -178,7 +178,7 @@ if(APPLE) set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER vcmieditor) endif() -target_link_libraries(vcmieditor ${VCMI_LIB_TARGET} Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) +target_link_libraries(vcmieditor vcmi Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) target_include_directories(vcmieditor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/scripting/erm/CMakeLists.txt b/scripting/erm/CMakeLists.txt index 99da95142..6e070e832 100644 --- a/scripting/erm/CMakeLists.txt +++ b/scripting/erm/CMakeLists.txt @@ -15,7 +15,7 @@ set(lib_HDRS ) add_library(vcmiERM SHARED ${lib_SRCS} ${lib_HDRS}) -target_link_libraries(vcmiERM Boost::boost ${VCMI_LIB_TARGET}) +target_link_libraries(vcmiERM Boost::boost vcmi) vcmi_set_output_dir(vcmiERM "scripting") enable_pch(vcmiERM) diff --git a/scripting/lua/CMakeLists.txt b/scripting/lua/CMakeLists.txt index a376c62c7..efc9c0242 100644 --- a/scripting/lua/CMakeLists.txt +++ b/scripting/lua/CMakeLists.txt @@ -83,7 +83,7 @@ set(lib_HDRS ) add_library(vcmiLua SHARED ${lib_SRCS} ${lib_HDRS}) -target_link_libraries(vcmiLua Boost::boost luajit::luajit ${VCMI_LIB_TARGET}) +target_link_libraries(vcmiLua Boost::boost luajit::luajit vcmi) vcmi_set_output_dir(vcmiLua "scripting") enable_pch(vcmiLua) From eba4347cbb4abc53752809b929bf021b512d22c4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 11 Feb 2024 20:42:38 +0200 Subject: [PATCH 089/250] Code cleanup --- .../src/main/java/eu/vcmi/vcmi/ServerService.java | 10 ---------- .../src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java | 4 ---- serverapp/EntryPoint.cpp | 13 ++++++------- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ServerService.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ServerService.java index 0afe30161..b0d2c0afa 100644 --- a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ServerService.java +++ b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ServerService.java @@ -58,15 +58,6 @@ public class ServerService extends Service void onClientRegistered(Messenger client); } - private static class ServerStartThread extends Thread - { - @Override - public void run() - { - NativeMethods.createServer(); - } - } - private static class IncomingClientMessageHandler extends Handler { private WeakReference mCallbackRef; @@ -88,7 +79,6 @@ public class ServerService extends Service callback.onClientRegistered(msg.replyTo); } NativeMethods.setupMsg(msg.replyTo); - new ServerStartThread().start(); break; default: super.handleMessage(msg); diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java index 035bf3265..012c2670c 100644 --- a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java +++ b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/VcmiSDLActivity.java @@ -93,10 +93,6 @@ public class VcmiSDLActivity extends SDLActivity protected void onNewIntent(final Intent intent) { Log.i(this, "Got new intent with action " + intent.getAction()); - if (NATIVE_ACTION_CREATE_SERVER.equals(intent.getAction())) - { - initService(); - } } @Override diff --git a/serverapp/EntryPoint.cpp b/serverapp/EntryPoint.cpp index d1b937f87..f473ff05d 100644 --- a/serverapp/EntryPoint.cpp +++ b/serverapp/EntryPoint.cpp @@ -18,25 +18,24 @@ #include -const std::string SERVER_NAME_AFFIX = "server"; -const std::string SERVER_NAME = GameConstants::VCMI_VERSION + std::string(" (") + SERVER_NAME_AFFIX + ')'; +static const std::string SERVER_NAME_AFFIX = "server"; +static 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) { - namespace po = boost::program_options; - po::options_description opts("Allowed options"); + boost::program_options::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") + ("port", boost::program_options::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); + boost::program_options::store(boost::program_options::parse_command_line(argc, argv, opts), options); } catch(boost::program_options::error & e) { @@ -44,7 +43,7 @@ static void handleCommandOptions(int argc, const char * argv[], boost::program_o } } - po::notify(options); + boost::program_options::notify(options); if(options.count("help")) { From 1fa3447ee7efdb339f34cfae15835def1a9750f3 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Thu, 8 Feb 2024 19:20:51 +0100 Subject: [PATCH 090/250] CI: Fix Ccache for branches other than develop in forks of vcmi/vcmi --- .github/workflows/github.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index c51cb8f7c..9c5b4a783 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -134,7 +134,7 @@ jobs: # ensure the ccache for each PR is separate so they don't interfere with each other # fall back to ccache of the vcmi/vcmi repo if no PR-specific ccache is found - - name: Ccache for PRs + - name: ccache for PRs uses: hendrikmuhs/ccache-action@v1.2 if: ${{ github.event.number != '' }} with: @@ -146,9 +146,9 @@ jobs: max-size: "5G" verbose: 2 - - name: Ccache for vcmi/vcmi's develop branch + - name: ccache for everything but PRs uses: hendrikmuhs/ccache-action@v1.2 - if: ${{ github.event.number == '' && github.ref == 'refs/heads/develop' }} + if: ${{ (github.repository == 'vcmi/vcmi' && github.event.number == '' && github.ref == 'refs/heads/develop') || github.repository != 'vcmi/vcmi' }} with: key: ${{ matrix.preset }}-no-PR restore-keys: | From 95b35a1063d9a117cf54f142f030495d4891ea8c Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Thu, 8 Feb 2024 22:57:26 +0100 Subject: [PATCH 091/250] CI: Accept JSON with comments, but not JSON5 in its entirety --- .github/workflows/github.yml | 2 +- CI/linux-qt6/validate_json.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 9c5b4a783..3f50f3368 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -124,7 +124,7 @@ jobs: # also, running it on multiple presets is redundant and slightly increases already long CI built times if: ${{ startsWith(matrix.preset, 'linux-clang-test') }} run: | - pip3 install json5 + pip3 install jstyleson python3 CI/linux-qt6/validate_json.py - name: Dependencies diff --git a/CI/linux-qt6/validate_json.py b/CI/linux-qt6/validate_json.py index 886eb8e7d..b57961eac 100755 --- a/CI/linux-qt6/validate_json.py +++ b/CI/linux-qt6/validate_json.py @@ -5,7 +5,8 @@ import sys from pathlib import Path from pprint import pprint -import json5 +# VCMI supports JSON with comments, but not JSON5 +import jstyleson validation_failed = False @@ -17,10 +18,10 @@ for path in sorted(Path(".").glob("**/*.json"), key=lambda path: str(path).lower try: with open(path_str, "r") as file: - json5.load(file) + jstyleson.load(file) print(f"✅ {path_str}") except Exception as exc: - print(f"❌ {str(exc).replace('', path_str)}") + print(f"❌ {path_str}: {exc}") validation_failed = True if validation_failed: From c577ea3e8d46a8ac9e8292780fecb9d178fe2a85 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 12:31:27 +0200 Subject: [PATCH 092/250] Fix potentially uninitialized values --- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 1 + AI/Nullkiller/Pathfinding/Actors.h | 26 ++++++++++----------- client/Client.cpp | 2 +- lib/networkPacks/NetPacksBase.h | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index f119359de..ffd529370 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -843,6 +843,7 @@ ExchangeCandidate HeroChainCalculationTask::calculateExchange( candidate.turns = carrierParentNode->turns; candidate.setCost(carrierParentNode->getCost() + otherParentNode->getCost() / 1000.0); candidate.moveRemains = carrierParentNode->moveRemains; + candidate.danger = carrierParentNode->danger; if(carrierParentNode->turns < otherParentNode->turns) { diff --git a/AI/Nullkiller/Pathfinding/Actors.h b/AI/Nullkiller/Pathfinding/Actors.h index c723c867f..2947aa487 100644 --- a/AI/Nullkiller/Pathfinding/Actors.h +++ b/AI/Nullkiller/Pathfinding/Actors.h @@ -51,24 +51,24 @@ protected: public: uint64_t chainMask; - bool isMovable; - bool allowUseResources; - bool allowBattle; - bool allowSpellCast; + bool isMovable = false; + bool allowUseResources = false; + bool allowBattle = false; + bool allowSpellCast = false; std::shared_ptr actorAction; const CGHeroInstance * hero; HeroRole heroRole; - const CCreatureSet * creatureSet; - const ChainActor * battleActor; - const ChainActor * castActor; - const ChainActor * resourceActor; - const ChainActor * carrierParent; - const ChainActor * otherParent; - const ChainActor * baseActor; + const CCreatureSet * creatureSet = nullptr; + const ChainActor * battleActor = nullptr; + const ChainActor * castActor = nullptr; + const ChainActor * resourceActor = nullptr; + const ChainActor * carrierParent = nullptr; + const ChainActor * otherParent = nullptr; + const ChainActor * baseActor = nullptr; int3 initialPosition; EPathfindingLayer layer; - uint32_t initialMovement; - uint32_t initialTurn; + uint32_t initialMovement = 0; + uint32_t initialTurn = 0; uint64_t armyValue; float heroFightingStrength; uint8_t actorExchangeCount; diff --git a/client/Client.cpp b/client/Client.cpp index 9ff148e9e..5711cf36f 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -537,7 +537,7 @@ void CClient::handlePack(CPack * pack) int CClient::sendRequest(const CPackForServer * request, PlayerColor player) { - static ui32 requestCounter = 0; + static ui32 requestCounter = 1; ui32 requestID = requestCounter++; logNetwork->trace("Sending a request \"%s\". It'll have an ID=%d.", typeid(*request).name(), requestID); diff --git a/lib/networkPacks/NetPacksBase.h b/lib/networkPacks/NetPacksBase.h index 9d765b0ff..c158a2953 100644 --- a/lib/networkPacks/NetPacksBase.h +++ b/lib/networkPacks/NetPacksBase.h @@ -64,7 +64,7 @@ struct DLL_LINKAGE Query : public CPackForClient struct DLL_LINKAGE CPackForServer : public CPack { mutable PlayerColor player = PlayerColor::NEUTRAL; - mutable si32 requestID; + mutable uint32_t requestID = 0; template void serialize(Handler &h) { From c03196257fe4466a92d0246da7fdc7d59729ab3a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 12:32:35 +0200 Subject: [PATCH 093/250] Fix "identical expressions on both sides of comparison" --- AI/VCAI/Goals/AbstractGoal.cpp | 91 +++++++++++++++++----------------- AI/VCAI/Goals/AbstractGoal.h | 10 ++-- lib/int3.h | 22 +++----- lib/rmg/float3.h | 6 +-- 4 files changed, 60 insertions(+), 69 deletions(-) diff --git a/AI/VCAI/Goals/AbstractGoal.cpp b/AI/VCAI/Goals/AbstractGoal.cpp index 3ed71f08d..301f7bdf3 100644 --- a/AI/VCAI/Goals/AbstractGoal.cpp +++ b/AI/VCAI/Goals/AbstractGoal.cpp @@ -109,51 +109,52 @@ bool AbstractGoal::operator==(const AbstractGoal & g) const return false; } -bool AbstractGoal::operator<(AbstractGoal & g) //for std::unique -{ - //TODO: make sure it gets goals consistent with == operator - if (goalType < g.goalType) - return true; - if (goalType > g.goalType) - return false; - if (hero < g.hero) - return true; - if (hero > g.hero) - return false; - if (tile < g.tile) - return true; - if (g.tile < tile) - return false; - if (objid < g.objid) - return true; - if (objid > g.objid) - return false; - if (town < g.town) - return true; - if (town > g.town) - return false; - if (value < g.value) - return true; - if (value > g.value) - return false; - if (priority < g.priority) - return true; - if (priority > g.priority) - return false; - if (resID < g.resID) - return true; - if (resID > g.resID) - return false; - if (bid < g.bid) - return true; - if (bid > g.bid) - return false; - if (aid < g.aid) - return true; - if (aid > g.aid) - return false; - return false; -} +// FIXME: unused code? +//bool AbstractGoal::operator<(AbstractGoal & g) //for std::unique +//{ +// //TODO: make sure it gets goals consistent with == operator +// if (goalType < g.goalType) +// return true; +// if (goalType > g.goalType) +// return false; +// if (hero < g.hero) +// return true; +// if (hero > g.hero) +// return false; +// if (tile < g.tile) +// return true; +// if (g.tile < tile) +// return false; +// if (objid < g.objid) +// return true; +// if (objid > g.objid) +// return false; +// if (town < g.town) +// return true; +// if (town > g.town) +// return false; +// if (value < g.value) +// return true; +// if (value > g.value) +// return false; +// if (priority < g.priority) +// return true; +// if (priority > g.priority) +// return false; +// if (resID < g.resID) +// return true; +// if (resID > g.resID) +// return false; +// if (bid < g.bid) +// return true; +// if (bid > g.bid) +// return false; +// if (aid < g.aid) +// return true; +// if (aid > g.aid) +// return false; +// return false; +//} //TODO: find out why the following are not generated automatically on MVS? bool TSubgoal::operator==(const TSubgoal & rhs) const diff --git a/AI/VCAI/Goals/AbstractGoal.h b/AI/VCAI/Goals/AbstractGoal.h index 2c54ca56e..5dc130682 100644 --- a/AI/VCAI/Goals/AbstractGoal.h +++ b/AI/VCAI/Goals/AbstractGoal.h @@ -165,16 +165,16 @@ namespace Goals virtual float accept(FuzzyHelper * f); virtual bool operator==(const AbstractGoal & g) const; - bool operator<(AbstractGoal & g); //final +// bool operator<(AbstractGoal & g); //final virtual bool fulfillsMe(Goals::TSubgoal goal) //TODO: multimethod instead of type check { return false; //use this method to check if goal is fulfilled by another (not equal) goal, operator == is handled spearately } - bool operator!=(const AbstractGoal & g) const - { - return !(*this == g); - } +// bool operator!=(const AbstractGoal & g) const +// { +// return !(*this == g); +// } template void serialize(Handler & h) { diff --git a/lib/int3.h b/lib/int3.h index 8f01fec33..7fe731d08 100644 --- a/lib/int3.h +++ b/lib/int3.h @@ -84,19 +84,13 @@ public: constexpr bool operator<(const int3 & i) const { - if (z < i.z) - return true; - if (z > i.z) - return false; - if (y < i.y) - return true; - if (y > i.y) - return false; - if (x < i.x) - return true; - if (x > i.x) - return false; - return false; + if (z != i.z) + return z < i.z; + + if (y != i.y) + return y < i.y; + + return x < i.x; } enum EDistanceFormula @@ -224,4 +218,4 @@ struct std::hash { std::size_t operator()(VCMI_LIB_WRAP_NAMESPACE(int3) const& pos) const noexcept { return hash_value(pos); } -}; \ No newline at end of file +}; diff --git a/lib/rmg/float3.h b/lib/rmg/float3.h index 8a7869ece..97bbc95fc 100644 --- a/lib/rmg/float3.h +++ b/lib/rmg/float3.h @@ -126,12 +126,8 @@ public: return true; if (y>i.y) return false; - if (xi.x) - return false; - return false; + return x Date: Mon, 12 Feb 2024 12:32:53 +0200 Subject: [PATCH 094/250] Fix invalid check --- launcher/updatedialog_moc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/updatedialog_moc.cpp b/launcher/updatedialog_moc.cpp index a6224a60a..5542fa903 100644 --- a/launcher/updatedialog_moc.cpp +++ b/launcher/updatedialog_moc.cpp @@ -96,7 +96,7 @@ void UpdateDialog::loadFromJson(const JsonNode & node) node["updateType"].getType() != JsonNode::JsonType::DATA_STRING || node["version"].getType() != JsonNode::JsonType::DATA_STRING || node["changeLog"].getType() != JsonNode::JsonType::DATA_STRING || - node.getType() != JsonNode::JsonType::DATA_STRUCT) //we need at least one link - other are optional + node["downloadLinks"].getType() != JsonNode::JsonType::DATA_STRUCT) //we need at least one link - other are optional { ui->plainTextEdit->setPlainText("Cannot read JSON from url or incorrect JSON data"); return; From b796ed86265ab6264037405b2732644617c1e72f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 12:33:09 +0200 Subject: [PATCH 095/250] Fix undefined behavior --- client/eventsSDL/InputHandler.cpp | 2 +- lib/ScopeGuard.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/eventsSDL/InputHandler.cpp b/client/eventsSDL/InputHandler.cpp index ae53dcf31..ae4494db4 100644 --- a/client/eventsSDL/InputHandler.cpp +++ b/client/eventsSDL/InputHandler.cpp @@ -304,7 +304,7 @@ void InputHandler::dispatchMainThread(const std::function & functor) auto heapFunctor = new std::function(functor); SDL_Event event; - event.type = SDL_USEREVENT; + event.user.type = SDL_USEREVENT; event.user.code = 0; event.user.data1 = static_cast (heapFunctor); event.user.data2 = nullptr; diff --git a/lib/ScopeGuard.h b/lib/ScopeGuard.h index bd8e0f6b5..de9b06a95 100644 --- a/lib/ScopeGuard.h +++ b/lib/ScopeGuard.h @@ -33,7 +33,7 @@ namespace vstd explicit ScopeGuard(Func && f): fire(true), - f(std::forward(f)) + f(std::move(f)) {} ~ScopeGuard() { From 392c360f88210a181a00cfd847f51aff5d83fe29 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 12:53:10 +0200 Subject: [PATCH 096/250] Replaced some usages of void * with more clear CPack * --- client/Client.cpp | 12 ++++++------ lib/gameState/CGameState.cpp | 4 ++-- lib/serializer/BinaryDeserializer.h | 2 +- server/CGameHandler.cpp | 6 +++--- server/CVCMIServer.cpp | 12 ++++++------ 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/client/Client.cpp b/client/Client.cpp index 5711cf36f..6628be6ab 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -59,8 +59,8 @@ template class CApplyOnCL; class CBaseForCLApply { public: - virtual void applyOnClAfter(CClient * cl, void * pack) const =0; - virtual void applyOnClBefore(CClient * cl, void * pack) const =0; + virtual void applyOnClAfter(CClient * cl, CPack * pack) const =0; + virtual void applyOnClBefore(CClient * cl, CPack * pack) const =0; virtual ~CBaseForCLApply(){} template static CBaseForCLApply * getApplier(const U * t = nullptr) @@ -72,13 +72,13 @@ public: template class CApplyOnCL : public CBaseForCLApply { public: - void applyOnClAfter(CClient * cl, void * pack) const override + void applyOnClAfter(CClient * cl, CPack * pack) const override { T * ptr = static_cast(pack); ApplyClientNetPackVisitor visitor(*cl, *cl->gameState()); ptr->visit(visitor); } - void applyOnClBefore(CClient * cl, void * pack) const override + void applyOnClBefore(CClient * cl, CPack * pack) const override { T * ptr = static_cast(pack); ApplyFirstClientNetPackVisitor visitor(*cl, *cl->gameState()); @@ -89,12 +89,12 @@ public: template<> class CApplyOnCL: public CBaseForCLApply { public: - void applyOnClAfter(CClient * cl, void * pack) const override + void applyOnClAfter(CClient * cl, CPack * pack) const override { logGlobal->error("Cannot apply on CL plain CPack!"); assert(0); } - void applyOnClBefore(CClient * cl, void * pack) const override + void applyOnClBefore(CClient * cl, CPack * pack) const override { logGlobal->error("Cannot apply on CL plain CPack!"); assert(0); diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 98460d32f..9fa865db0 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -59,7 +59,7 @@ template class CApplyOnGS; class CBaseForGSApply { public: - virtual void applyOnGS(CGameState *gs, void *pack) const =0; + virtual void applyOnGS(CGameState *gs, CPack * pack) const =0; virtual ~CBaseForGSApply() = default; template static CBaseForGSApply *getApplier(const U * t=nullptr) { @@ -70,7 +70,7 @@ public: template class CApplyOnGS : public CBaseForGSApply { public: - void applyOnGS(CGameState *gs, void *pack) const override + void applyOnGS(CGameState *gs, CPack * pack) const override { T *ptr = static_cast(pack); diff --git a/lib/serializer/BinaryDeserializer.h b/lib/serializer/BinaryDeserializer.h index 222ed2364..d18d9188d 100644 --- a/lib/serializer/BinaryDeserializer.h +++ b/lib/serializer/BinaryDeserializer.h @@ -315,7 +315,7 @@ public: void ptrAllocated(const T *ptr, ui32 pid) { if(smartPointerSerialization && pid != 0xffffffff) - loadedPointers[pid] = (void*)ptr; //add loaded pointer to our lookup map; cast is to avoid errors with const T* pt + loadedPointers[pid] = static_cast(ptr); //add loaded pointer to our lookup map; cast is to avoid errors with const T* pt } template void registerType(const Base * b = nullptr, const Derived * d = nullptr) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 1fcca7397..aaa7793f1 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -82,7 +82,7 @@ template class CApplyOnGH; class CBaseForGHApply { public: - virtual bool applyOnGH(CGameHandler * gh, CGameState * gs, void * pack) const =0; + virtual bool applyOnGH(CGameHandler * gh, CGameState * gs, CPack * pack) const =0; virtual ~CBaseForGHApply(){} template static CBaseForGHApply *getApplier(const U * t=nullptr) { @@ -93,7 +93,7 @@ public: template class CApplyOnGH : public CBaseForGHApply { public: - bool applyOnGH(CGameHandler * gh, CGameState * gs, void * pack) const override + bool applyOnGH(CGameHandler * gh, CGameState * gs, CPack * pack) const override { T *ptr = static_cast(pack); try @@ -116,7 +116,7 @@ template <> class CApplyOnGH : public CBaseForGHApply { public: - bool applyOnGH(CGameHandler * gh, CGameState * gs, void * pack) const override + bool applyOnGH(CGameHandler * gh, CGameState * gs, CPack * pack) const override { logGlobal->error("Cannot apply on GH plain CPack!"); assert(0); diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 1ec3d3299..0d21c710e 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -31,8 +31,8 @@ template class CApplyOnServer; class CBaseForServerApply { public: - virtual bool applyOnServerBefore(CVCMIServer * srv, void * pack) const =0; - virtual void applyOnServerAfter(CVCMIServer * srv, void * pack) const =0; + virtual bool applyOnServerBefore(CVCMIServer * srv, CPack * pack) const =0; + virtual void applyOnServerAfter(CVCMIServer * srv, CPack * pack) const =0; virtual ~CBaseForServerApply() {} template static CBaseForServerApply * getApplier(const U * t = nullptr) { @@ -43,7 +43,7 @@ public: template class CApplyOnServer : public CBaseForServerApply { public: - bool applyOnServerBefore(CVCMIServer * srv, void * pack) const override + bool applyOnServerBefore(CVCMIServer * srv, CPack * pack) const override { T * ptr = static_cast(pack); ClientPermissionsCheckerNetPackVisitor checker(*srv); @@ -59,7 +59,7 @@ public: return false; } - void applyOnServerAfter(CVCMIServer * srv, void * pack) const override + void applyOnServerAfter(CVCMIServer * srv, CPack * pack) const override { T * ptr = static_cast(pack); ApplyOnServerAfterAnnounceNetPackVisitor applier(*srv); @@ -71,13 +71,13 @@ template <> class CApplyOnServer : public CBaseForServerApply { public: - bool applyOnServerBefore(CVCMIServer * srv, void * pack) const override + bool applyOnServerBefore(CVCMIServer * srv, CPack * pack) const override { logGlobal->error("Cannot apply plain CPack!"); assert(0); return false; } - void applyOnServerAfter(CVCMIServer * srv, void * pack) const override + void applyOnServerAfter(CVCMIServer * srv, CPack * pack) const override { logGlobal->error("Cannot apply plain CPack!"); assert(0); From 6db405167daabe0493b7d39fca6d19ad4568b591 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 12:59:22 +0200 Subject: [PATCH 097/250] Clarified some (im)possible null dereferences --- lib/serializer/BinaryDeserializer.h | 2 +- server/battles/BattleActionProcessor.cpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/serializer/BinaryDeserializer.h b/lib/serializer/BinaryDeserializer.h index d18d9188d..222ed2364 100644 --- a/lib/serializer/BinaryDeserializer.h +++ b/lib/serializer/BinaryDeserializer.h @@ -315,7 +315,7 @@ public: void ptrAllocated(const T *ptr, ui32 pid) { if(smartPointerSerialization && pid != 0xffffffff) - loadedPointers[pid] = static_cast(ptr); //add loaded pointer to our lookup map; cast is to avoid errors with const T* pt + loadedPointers[pid] = (void*)ptr; //add loaded pointer to our lookup map; cast is to avoid errors with const T* pt } template void registerType(const Base * b = nullptr, const Derived * d = nullptr) diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index 140ebead9..653cbc9db 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -1573,8 +1573,11 @@ bool BattleActionProcessor::makePlayerBattleAction(const CBattleInfoCallback & b else { auto active = battle.battleActiveUnit(); - if(!active && gameHandler->complain("No active unit in battle!")) + if(!active) + { + gameHandler->complain("No active unit in battle!"); return false; + } if (ba.isUnitAction() && ba.stackNumber != active->unitId()) { @@ -1584,8 +1587,11 @@ bool BattleActionProcessor::makePlayerBattleAction(const CBattleInfoCallback & b auto unitOwner = battle.battleGetOwner(active); - if(player != unitOwner && gameHandler->complain("Can not make actions in battles you are not part of!")) + if(player != unitOwner) + { + gameHandler->complain("Can not make actions in battles you are not part of!"); return false; + } } return makeBattleActionImpl(battle, ba); From 6e399eb21a28fda6fdf13594465626b04876957a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 13:22:54 +0200 Subject: [PATCH 098/250] Make some non-const static variables const or constexpr --- client/gui/CGuiHandler.cpp | 2 +- client/lobby/CBonusSelection.cpp | 2 +- client/lobby/CSelectionBase.cpp | 2 +- client/mainmenu/CMainMenu.cpp | 4 ++-- client/mainmenu/CMainMenu.h | 2 +- client/render/CBitmapHandler.cpp | 8 ++++---- client/windows/CCastleInterface.cpp | 4 ++-- client/windows/CPuzzleWindow.cpp | 2 +- client/windows/GUIClasses.cpp | 2 +- launcher/modManager/cdownloadmanager_moc.cpp | 4 +--- launcher/modManager/cmodlistmodel_moc.cpp | 2 +- lib/CGeneralTextHandler.cpp | 2 +- lib/JsonDetail.cpp | 2 +- lib/battle/BattleInfo.cpp | 2 +- mapeditor/Animation.cpp | 2 +- 15 files changed, 20 insertions(+), 22 deletions(-) diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index 2bffc662c..d8d56eb2c 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -191,7 +191,7 @@ void CGuiHandler::drawFPSCounter() int y = screen->h-20; int width3digitFPSIncludingPadding = 48; int heightFPSTextIncludingPadding = 11; - static SDL_Rect overlay = { x, y, width3digitFPSIncludingPadding, heightFPSTextIncludingPadding}; + SDL_Rect overlay = { x, y, width3digitFPSIncludingPadding, heightFPSTextIncludingPadding}; uint32_t black = SDL_MapRGB(screen->format, 10, 10, 10); SDL_FillRect(screen, &overlay, black); diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 558b97a15..c000f0a0e 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -137,7 +137,7 @@ void CBonusSelection::createBonusesIcons() const std::vector & bonDescs = scenario.travelOptions.bonusesToChoose; groupBonuses = std::make_shared(std::bind(&IServerAPI::setCampaignBonus, CSH, _1)); - static const char * bonusPics[] = + constexpr std::array bonusPics = { "SPELLBON.DEF", // Spell "TWCRPORT.DEF", // Monster diff --git a/client/lobby/CSelectionBase.cpp b/client/lobby/CSelectionBase.cpp index 9e20faaa8..86bb6ed26 100644 --- a/client/lobby/CSelectionBase.cpp +++ b/client/lobby/CSelectionBase.cpp @@ -154,7 +154,7 @@ InfoCard::InfoCard() iconDifficulty = std::make_shared(0); { - static const char * difButns[] = {"GSPBUT3.DEF", "GSPBUT4.DEF", "GSPBUT5.DEF", "GSPBUT6.DEF", "GSPBUT7.DEF"}; + constexpr std::array difButns = {"GSPBUT3.DEF", "GSPBUT4.DEF", "GSPBUT5.DEF", "GSPBUT6.DEF", "GSPBUT7.DEF"}; for(int i = 0; i < 5; i++) { diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 4ff244b9b..ab2d63766 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -278,9 +278,9 @@ CMainMenuConfig::CMainMenuConfig() } -CMainMenuConfig & CMainMenuConfig::get() +const CMainMenuConfig & CMainMenuConfig::get() { - static CMainMenuConfig config; + static const CMainMenuConfig config; return config; } diff --git a/client/mainmenu/CMainMenu.h b/client/mainmenu/CMainMenu.h index 7ef4fef8d..32576d85b 100644 --- a/client/mainmenu/CMainMenu.h +++ b/client/mainmenu/CMainMenu.h @@ -125,7 +125,7 @@ public: class CMainMenuConfig { public: - static CMainMenuConfig & get(); + static const CMainMenuConfig & get(); const JsonNode & getConfig() const; const JsonNode & getCampaigns() const; diff --git a/client/render/CBitmapHandler.cpp b/client/render/CBitmapHandler.cpp index 79b597480..a6e0836f0 100644 --- a/client/render/CBitmapHandler.cpp +++ b/client/render/CBitmapHandler.cpp @@ -157,11 +157,11 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(const ImagePath & path) ret->format->palette->colors[0].g == 0 && ret->format->palette->colors[0].b == 255 ) { - static SDL_Color shadow[3] = + constexpr std::array shadow = { - { 0, 0, 0, 0},// 100% - transparency - { 0, 0, 0, 32},// 75% - shadow border, - { 0, 0, 0, 128},// 50% - shadow body + SDL_Color{ 0, 0, 0, 0},// 100% - transparency + SDL_Color{ 0, 0, 0, 32},// 75% - shadow border, + SDL_Color{ 0, 0, 0, 128},// 50% - shadow body }; CSDL_Ext::setColorKey(ret, ret->format->palette->colors[0]); diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index d8a80d0f2..2f398a116 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1427,11 +1427,11 @@ CHallInterface::CBuildingBox::CBuildingBox(int x, int y, const CGTownInstance * state = LOCPLINT->cb->canBuildStructure(town, building->bid); - static int panelIndex[12] = + constexpr std::array panelIndex = { 3, 3, 3, 0, 0, 2, 2, 1, 2, 2, 3, 3 }; - static int iconIndex[12] = + constexpr std::array iconIndex = { -1, -1, -1, 0, 0, 1, 2, -1, 1, 1, -1, -1 }; diff --git a/client/windows/CPuzzleWindow.cpp b/client/windows/CPuzzleWindow.cpp index 79edef9cb..8b5f70edb 100644 --- a/client/windows/CPuzzleWindow.cpp +++ b/client/windows/CPuzzleWindow.cpp @@ -76,7 +76,7 @@ void CPuzzleWindow::showAll(Canvas & to) void CPuzzleWindow::show(Canvas & to) { - static int animSpeed = 2; + constexpr int animSpeed = 2; if(currentAlpha < animSpeed) { diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index bf1489567..71d3d52f6 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1558,7 +1558,7 @@ CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner): //data for information table: // fields[row][column] = list of id's of players for this box - static std::vector< std::vector< PlayerColor > > SThievesGuildInfo::* fields[] = + constexpr std::vector< std::vector< PlayerColor > > SThievesGuildInfo::* fields[] = { &SThievesGuildInfo::numOfTowns, &SThievesGuildInfo::numOfHeroes, &SThievesGuildInfo::gold, &SThievesGuildInfo::woodOre, &SThievesGuildInfo::mercSulfCrystGems, &SThievesGuildInfo::obelisks, &SThievesGuildInfo::artifacts, &SThievesGuildInfo::army, &SThievesGuildInfo::income }; diff --git a/launcher/modManager/cdownloadmanager_moc.cpp b/launcher/modManager/cdownloadmanager_moc.cpp index a0e3eddaa..563ce0b7e 100644 --- a/launcher/modManager/cdownloadmanager_moc.cpp +++ b/launcher/modManager/cdownloadmanager_moc.cpp @@ -54,9 +54,7 @@ CDownloadManager::FileEntry & CDownloadManager::getEntry(QNetworkReply * reply) if(entry.reply == reply) return entry; } - assert(0); - static FileEntry errorValue; - return errorValue; + throw std::runtime_error("Failed to find download entry"); } void CDownloadManager::downloadFinished(QNetworkReply * reply) diff --git a/launcher/modManager/cmodlistmodel_moc.cpp b/launcher/modManager/cmodlistmodel_moc.cpp index cece26b53..5ec35e400 100644 --- a/launcher/modManager/cmodlistmodel_moc.cpp +++ b/launcher/modManager/cmodlistmodel_moc.cpp @@ -38,7 +38,7 @@ QString CModListModel::modIndexToName(const QModelIndex & index) const QString CModListModel::modTypeName(QString modTypeID) const { - static QMap modTypes = { + static const QMap modTypes = { {"Translation", tr("Translation")}, {"Town", tr("Town") }, {"Test", tr("Test") }, diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index aa963c965..ba8888e1a 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -502,7 +502,7 @@ CGeneralTextHandler::CGeneralTextHandler(): readToVector("core.mineevnt", "DATA/MINEEVNT.TXT" ); readToVector("core.xtrainfo", "DATA/XTRAINFO.TXT" ); - static const char * QE_MOD_COMMANDS = "DATA/QECOMMANDS.TXT"; + static const std::string QE_MOD_COMMANDS = "DATA/QECOMMANDS.TXT"; if (CResourceHandler::get()->existsResource(TextPath::builtin(QE_MOD_COMMANDS))) readToVector("vcmi.quickExchange", QE_MOD_COMMANDS); diff --git a/lib/JsonDetail.cpp b/lib/JsonDetail.cpp index 16e9dfddd..ab2d4e6da 100644 --- a/lib/JsonDetail.cpp +++ b/lib/JsonDetail.cpp @@ -1259,7 +1259,7 @@ namespace Validation const TFormatMap & getKnownFormats() { - static TFormatMap knownFormats = createFormatMap(); + static const TFormatMap knownFormats = createFormatMap(); return knownFormats; } diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 9af24d57b..dfa02115d 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -443,7 +443,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const } //native terrain bonuses - static auto nativeTerrain = std::make_shared(); + auto nativeTerrain = std::make_shared(); curB->addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::STACKS_SPEED, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID())->addLimiter(nativeTerrain)); curB->addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID(), BonusSubtypeID(PrimarySkill::ATTACK))->addLimiter(nativeTerrain)); diff --git a/mapeditor/Animation.cpp b/mapeditor/Animation.cpp index 9d9c82ffd..4c12a220c 100644 --- a/mapeditor/Animation.cpp +++ b/mapeditor/Animation.cpp @@ -161,7 +161,7 @@ DefFile::DefFile(std::string Name): #endif // 0 //First 8 colors in def palette used for transparency - static QRgb H3Palette[8] = + constexpr std::array H3Palette = { qRgba(0, 0, 0, 0), // 100% - transparency qRgba(0, 0, 0, 32), // 75% - shadow border, From 4c70abbeb5b6e2008addbe2596097930be37ad04 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 13:49:45 +0200 Subject: [PATCH 099/250] Reduced usage of global variables - removed or made const / constexpr --- AI/BattleAI/AttackPossibility.h | 1 - AI/BattleAI/BattleAI.cpp | 2 -- AI/BattleAI/CMakeLists.txt | 2 -- AI/BattleAI/common.cpp | 23 ------------ AI/BattleAI/common.h | 26 -------------- AI/BattleAI/main.cpp | 2 +- AI/EmptyAI/main.cpp | 1 - .../Analyzers/DangerHitMapAnalyzer.cpp | 4 +-- .../Analyzers/DangerHitMapAnalyzer.h | 2 +- AI/Nullkiller/Analyzers/HeroManager.cpp | 6 ++-- AI/Nullkiller/Analyzers/HeroManager.h | 6 ++-- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 2 +- AI/Nullkiller/Pathfinding/Actors.cpp | 2 +- AI/Nullkiller/main.cpp | 2 +- AI/StupidAI/main.cpp | 2 +- AI/VCAI/BuildingManager.cpp | 2 +- AI/VCAI/main.cpp | 2 +- client/CMT.cpp | 3 +- client/CMusicHandler.cpp | 2 +- client/windows/CCastleInterface.cpp | 36 ++++++++----------- client/windows/CSpellWindow.cpp | 4 ++- lib/rmg/Zone.cpp | 2 +- lib/rmg/Zone.h | 2 +- scripting/lua/LuaScriptModule.cpp | 2 +- 24 files changed, 39 insertions(+), 99 deletions(-) delete mode 100644 AI/BattleAI/common.cpp delete mode 100644 AI/BattleAI/common.h diff --git a/AI/BattleAI/AttackPossibility.h b/AI/BattleAI/AttackPossibility.h index 2181d883a..b8ff77218 100644 --- a/AI/BattleAI/AttackPossibility.h +++ b/AI/BattleAI/AttackPossibility.h @@ -10,7 +10,6 @@ #pragma once #include "../../lib/battle/CUnitState.h" #include "../../CCallback.h" -#include "common.h" #include "StackWithBonuses.h" #define BATTLE_TRACE_LEVEL 0 diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 631c163a0..37ba4f270 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -49,7 +49,6 @@ CBattleAI::~CBattleAI() void CBattleAI::initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB) { - setCbc(CB); env = ENV; cb = CB; playerID = *CB->getPlayerID(); @@ -121,7 +120,6 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack ) }; BattleAction result = BattleAction::makeDefend(stack); - setCbc(cb); //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too) auto start = std::chrono::high_resolution_clock::now(); diff --git a/AI/BattleAI/CMakeLists.txt b/AI/BattleAI/CMakeLists.txt index 335c92f5c..e51fa3072 100644 --- a/AI/BattleAI/CMakeLists.txt +++ b/AI/BattleAI/CMakeLists.txt @@ -2,7 +2,6 @@ set(battleAI_SRCS AttackPossibility.cpp BattleAI.cpp BattleEvaluator.cpp - common.cpp EnemyInfo.cpp PossibleSpellcast.cpp PotentialTargets.cpp @@ -17,7 +16,6 @@ set(battleAI_HEADERS AttackPossibility.h BattleAI.h BattleEvaluator.h - common.h EnemyInfo.h PotentialTargets.h PossibleSpellcast.h diff --git a/AI/BattleAI/common.cpp b/AI/BattleAI/common.cpp deleted file mode 100644 index 4a467c8cc..000000000 --- a/AI/BattleAI/common.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * common.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 "common.h" - -std::shared_ptr cbc; - -void setCbc(std::shared_ptr cb) -{ - cbc = cb; -} - -std::shared_ptr getCbc() -{ - return cbc; -} diff --git a/AI/BattleAI/common.h b/AI/BattleAI/common.h deleted file mode 100644 index dfc9b4623..000000000 --- a/AI/BattleAI/common.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * common.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 CBattleCallback; - -template -const Val getValOr(const std::map &Map, const Key &key, const Val2 defaultValue) -{ - //returning references here won't work: defaultValue must be converted into Val, creating temporary - auto i = Map.find(key); - if(i != Map.end()) - return i->second; - else - return defaultValue; -} - -void setCbc(std::shared_ptr cb); -std::shared_ptr getCbc(); diff --git a/AI/BattleAI/main.cpp b/AI/BattleAI/main.cpp index 101491d93..7e8b93c05 100644 --- a/AI/BattleAI/main.cpp +++ b/AI/BattleAI/main.cpp @@ -15,7 +15,7 @@ #define strcpy_s(a, b, c) strncpy(a, c, b) #endif -static const char *g_cszAiName = "Battle AI"; +static const char * const g_cszAiName = "Battle AI"; extern "C" DLL_EXPORT int GetGlobalAiVersion() { diff --git a/AI/EmptyAI/main.cpp b/AI/EmptyAI/main.cpp index e6ae9483c..e67974b9e 100644 --- a/AI/EmptyAI/main.cpp +++ b/AI/EmptyAI/main.cpp @@ -11,7 +11,6 @@ #include "CEmptyAI.h" -std::set ais; extern "C" DLL_EXPORT int GetGlobalAiVersion() { return AI_INTERFACE_VER; diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index ce74645a1..ccf6b7ecb 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -16,7 +16,7 @@ namespace NKAI { -HitMapInfo HitMapInfo::NoThreat; +const HitMapInfo HitMapInfo::NoThreat; double HitMapInfo::value() const { @@ -285,8 +285,6 @@ const HitMapNode & DangerHitMapAnalyzer::getTileThreat(const int3 & tile) const return hitMap[tile.x][tile.y][tile.z]; } -const std::set empty = {}; - std::set DangerHitMapAnalyzer::getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const { std::set result; diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h index 45538c99b..fc2890846 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h @@ -18,7 +18,7 @@ struct AIPath; struct HitMapInfo { - static HitMapInfo NoThreat; + static const HitMapInfo NoThreat; uint64_t danger; uint8_t turn; diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 4131182cb..42a3ae29f 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -17,7 +17,7 @@ namespace NKAI { -SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator( +const SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator( { std::make_shared( std::map @@ -46,7 +46,7 @@ SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluato std::make_shared() }); -SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator( +const SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator( { std::make_shared( std::map @@ -332,7 +332,7 @@ void WisdomRule::evaluateScore(const CGHeroInstance * hero, SecondarySkill skill score += 1.5; } -std::vector AtLeastOneMagicRule::magicSchools = { +const std::vector AtLeastOneMagicRule::magicSchools = { SecondarySkill::AIR_MAGIC, SecondarySkill::EARTH_MAGIC, SecondarySkill::FIRE_MAGIC, diff --git a/AI/Nullkiller/Analyzers/HeroManager.h b/AI/Nullkiller/Analyzers/HeroManager.h index a7744ad1f..fa44198b7 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.h +++ b/AI/Nullkiller/Analyzers/HeroManager.h @@ -58,8 +58,8 @@ public: class DLL_EXPORT HeroManager : public IHeroManager { private: - static SecondarySkillEvaluator wariorSkillsScores; - static SecondarySkillEvaluator scountSkillsScores; + static const SecondarySkillEvaluator wariorSkillsScores; + static const SecondarySkillEvaluator scountSkillsScores; CCallback * cb; //this is enough, but we downcast from CCallback const Nullkiller * ai; @@ -114,7 +114,7 @@ public: class AtLeastOneMagicRule : public ISecondarySkillRule { private: - static std::vector magicSchools; + static const std::vector magicSchools; public: void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override; diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index ffd529370..863549dd7 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -332,7 +332,7 @@ std::vector AINodeStorage::calculateNeighbours( return neighbours; } -EPathfindingLayer phisycalLayers[2] = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL}; +constexpr std::array phisycalLayers = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL}; bool AINodeStorage::increaseHeroChainTurnLimit() { diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index 900d65506..71259fa05 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -18,7 +18,7 @@ using namespace NKAI; -CCreatureSet emptyArmy; +const CCreatureSet emptyArmy; bool HeroExchangeArmy::needsLastStack() const { diff --git a/AI/Nullkiller/main.cpp b/AI/Nullkiller/main.cpp index fff944c8d..695d000b3 100644 --- a/AI/Nullkiller/main.cpp +++ b/AI/Nullkiller/main.cpp @@ -14,7 +14,7 @@ #define strcpy_s(a, b, c) strncpy(a, c, b) #endif -static const char * g_cszAiName = "Nullkiller"; +static const char * const g_cszAiName = "Nullkiller"; extern "C" DLL_EXPORT int GetGlobalAiVersion() { diff --git a/AI/StupidAI/main.cpp b/AI/StupidAI/main.cpp index 93f2ff517..5cf31e497 100644 --- a/AI/StupidAI/main.cpp +++ b/AI/StupidAI/main.cpp @@ -16,7 +16,7 @@ #define strcpy_s(a, b, c) strncpy(a, c, b) #endif -static const char *g_cszAiName = "Stupid AI 0.1"; +static const char * const g_cszAiName = "Stupid AI 0.1"; extern "C" DLL_EXPORT int GetGlobalAiVersion() { diff --git a/AI/VCAI/BuildingManager.cpp b/AI/VCAI/BuildingManager.cpp index 843c54d3f..f791a594f 100644 --- a/AI/VCAI/BuildingManager.cpp +++ b/AI/VCAI/BuildingManager.cpp @@ -148,7 +148,7 @@ BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_ static const std::vector unitGrowth = { BuildingID::HORDE_1, BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR }; static const std::vector _spells = { BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5 }; -static const std::vector extra = { BuildingID::MARKETPLACE, BuildingID::BLACKSMITH, BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, +static const std::vector extra = { BuildingID::MARKETPLACE, BuildingID::BLACKSMITH, BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3, BuildingID::SPECIAL_4, BuildingID::SHIPYARD }; // all remaining buildings bool BuildingManager::getBuildingOptions(const CGTownInstance * t) diff --git a/AI/VCAI/main.cpp b/AI/VCAI/main.cpp index 3a88b2716..1e262024e 100644 --- a/AI/VCAI/main.cpp +++ b/AI/VCAI/main.cpp @@ -14,7 +14,7 @@ #define strcpy_s(a, b, c) strncpy(a, c, b) #endif -static const char * g_cszAiName = "VCAI"; +static const char * const g_cszAiName = "VCAI"; extern "C" DLL_EXPORT int GetGlobalAiVersion() { diff --git a/client/CMT.cpp b/client/CMT.cpp index 5305a273e..956851095 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -56,7 +56,6 @@ namespace po = boost::program_options; namespace po_style = boost::program_options::command_line_style; static std::atomic quitRequestedDuringOpeningPlayback = false; -static po::variables_map vm; #ifndef VCMI_IOS void processCommand(const std::string &message); @@ -118,6 +117,8 @@ int main(int argc, char * argv[]) #endif std::cout << "Starting... " << std::endl; po::options_description opts("Allowed options"); + po::variables_map vm; + opts.add_options() ("help,h", "display help and exit") ("version,v", "display version information and exit") diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index b3089375a..1339a4de9 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -30,7 +30,7 @@ #define VCMI_SOUND_FILE(y) #y, // sounds mapped to soundBase enum -static std::string sounds[] = { +static const std::string sounds[] = { "", // invalid "", // todo VCMI_SOUND_LIST diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 2f398a116..16fdd2365 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -562,27 +562,6 @@ void HeroSlots::swapArmies() LOCPLINT->cb->swapGarrisonHero(town); } -class SORTHELP -{ -public: - bool operator() (const CIntObject * a, const CIntObject * b) - { - auto b1 = dynamic_cast(a); - auto b2 = dynamic_cast(b); - - if(!b1 && !b2) - return intptr_t(a) < intptr_t(b); - if(b1 && !b2) - return false; - if(!b1 && b2) - return true; - - return (*b1)<(*b2); - } -}; - -SORTHELP buildSorter; - CCastleBuildings::CCastleBuildings(const CGTownInstance* Town): town(Town), selectedBuilding(nullptr) @@ -650,6 +629,21 @@ void CCastleBuildings::recreate() buildings.push_back(std::make_shared(this, town, toAdd)); } + auto const & buildSorter = [] (const CIntObject * a, const CIntObject * b) + { + auto b1 = dynamic_cast(a); + auto b2 = dynamic_cast(b); + + if(!b1 && !b2) + return intptr_t(a) < intptr_t(b); + if(b1 && !b2) + return false; + if(!b1 && b2) + return true; + + return (*b1)<(*b2); + }; + boost::sort(children, buildSorter); //TODO: create building in blit order } diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 9c41f673e..2ecc9acf4 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -94,7 +94,7 @@ public: return A->getNameTranslated() < B->getNameTranslated(); } -} spellsorter; +}; CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _myInt, bool openOnBattleSpells): CWindowObject(PLAYER_COLORED | (settings["gameTweaks"]["enableLargeSpellbook"].Bool() ? BORDERED : 0)), @@ -293,6 +293,8 @@ void CSpellWindow::processSpells() if(!spell->isCreatureAbility() && myHero->canCastThisSpell(spell) && searchTextFound) mySpells.push_back(spell); } + + SpellbookSpellSorter spellsorter; std::sort(mySpells.begin(), mySpells.end(), spellsorter); //initializing sizes of spellbook's parts diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index 09f563727..dad4f22d7 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -19,7 +19,7 @@ VCMI_LIB_NAMESPACE_BEGIN -std::function AREA_NO_FILTER = [](const int3 & t) +const std::function AREA_NO_FILTER = [](const int3 & t) { return true; }; diff --git a/lib/rmg/Zone.h b/lib/rmg/Zone.h index cce97f2e4..0d2376ad8 100644 --- a/lib/rmg/Zone.h +++ b/lib/rmg/Zone.h @@ -30,7 +30,7 @@ class CMapGenerator; class Modificator; class CRandomGenerator; -extern std::function AREA_NO_FILTER; +extern const std::function AREA_NO_FILTER; typedef std::list> TModificators; diff --git a/scripting/lua/LuaScriptModule.cpp b/scripting/lua/LuaScriptModule.cpp index fb3ef8a76..99ac829e4 100644 --- a/scripting/lua/LuaScriptModule.cpp +++ b/scripting/lua/LuaScriptModule.cpp @@ -17,7 +17,7 @@ #define strcpy_s(a, b, c) strncpy(a, c, b) #endif -static const char *g_cszAiName = "Lua interpreter"; +static const char * const g_cszAiName = "Lua interpreter"; VCMI_LIB_NAMESPACE_BEGIN From 62148e1506550549776047bb750e573d41d74982 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 15:08:46 +0200 Subject: [PATCH 100/250] Fix build --- client/CMT.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index 956851095..c26f53eb4 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -201,17 +201,17 @@ int main(int argc, char * argv[]) preinitDLL(::console, false); Settings session = settings.write["session"]; - auto setSettingBool = [](std::string key, std::string arg) { + auto setSettingBool = [&](std::string key, std::string arg) { Settings s = settings.write(vstd::split(key, "/")); - if(::vm.count(arg)) + if(vm.count(arg)) s->Bool() = true; else if(s->isNull()) s->Bool() = false; }; - auto setSettingInteger = [](std::string key, std::string arg, si64 defaultValue) { + auto setSettingInteger = [&](std::string key, std::string arg, si64 defaultValue) { Settings s = settings.write(vstd::split(key, "/")); - if(::vm.count(arg)) - s->Integer() = ::vm[arg].as(); + if(vm.count(arg)) + s->Integer() = vm[arg].as(); else if(s->isNull()) s->Integer() = defaultValue; }; From 763e18d202e451d2decfa42ec6c44964d8d33d7a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 16:35:18 +0200 Subject: [PATCH 101/250] Fix symlink target - use absolute path --- lib/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 1d0a13f4f..7cfd624a6 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -721,8 +721,8 @@ if(COPY_CONFIG_ON_BUILD) add_custom_command(TARGET vcmi POST_BUILD COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ../config ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ../Mods ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ${CMAKE_SOURCE_DIR}/config ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ${CMAKE_SOURCE_DIR}/Mods ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods ) endif() From 74f9b03516044342f56b7df61606e04fc3311d13 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 16:36:13 +0200 Subject: [PATCH 102/250] Fix crash on closing client while server is active --- client/CServerHandler.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 17bac420d..05b86ea8f 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -108,9 +108,14 @@ public: CServerHandler::~CServerHandler() { + if (serverRunner) + serverRunner->shutdown(); networkHandler->stop(); try { + if (serverRunner) + serverRunner->wait(); + serverRunner.reset(); threadNetwork.join(); } catch (const std::runtime_error & e) From 7c34d48258d0fa780d481b87cea6f070b25cbf2d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 18:57:20 +0200 Subject: [PATCH 103/250] Minor fixes and corrections to network-related code. No functionality changes. --- client/CServerHandler.cpp | 14 +++++++------- client/CServerHandler.h | 4 ++-- lobby/LobbyServer.cpp | 2 +- server/CVCMIServer.cpp | 27 +++++++++++++-------------- server/NetPacksLobbyServer.cpp | 10 +++++----- 5 files changed, 28 insertions(+), 29 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 2899906a9..85d6bebc3 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -163,7 +163,7 @@ void CServerHandler::threadRunNetwork() logGlobal->info("Ending network thread"); } -void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode newServerMode, const std::vector & names) +void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode newServerMode, const std::vector & playerNames) { hostClientId = -1; setState(EClientState::NONE); @@ -172,15 +172,15 @@ void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen th = std::make_unique(); logicConnection.reset(); si = std::make_shared(); - playerNames.clear(); + localPlayerNames.clear(); si->difficulty = 1; si->mode = mode; screenType = screen; - myNames.clear(); - if(!names.empty()) //if have custom set of player names - use it - myNames = names; + localPlayerNames.clear(); + if(!playerNames.empty()) //if have custom set of player names - use it + localPlayerNames = playerNames; else - myNames.push_back(settings["general"]["playerName"].String()); + localPlayerNames.push_back(settings["general"]["playerName"].String()); } GlobalLobbyClient & CServerHandler::getGlobalLobby() @@ -421,7 +421,7 @@ void CServerHandler::sendClientConnecting() const { LobbyClientConnected lcc; lcc.uuid = uuid; - lcc.names = myNames; + lcc.names = localPlayerNames; lcc.mode = si->mode; sendLobbyPack(lcc); } diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 59573676d..fc48e8cfc 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -101,7 +101,7 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor std::unique_ptr lobbyClient; std::unique_ptr> applier; std::shared_ptr mapToStart; - std::vector myNames; + std::vector localPlayerNames; std::shared_ptr highScoreCalc; boost::thread threadRunLocalServer; @@ -148,7 +148,7 @@ public: CServerHandler(); ~CServerHandler(); - void resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode serverMode, const std::vector & names); + void resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode serverMode, const std::vector & playerNames); void startLocalServerAndConnect(bool connectToLobby); void connectToServer(const std::string & addr, const ui16 port); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 70fd2470a..86d846e0e 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -22,7 +22,7 @@ bool LobbyServer::isAccountNameValid(const std::string & accountName) const if(accountName.size() < 4) return false; - if(accountName.size() < 20) + if(accountName.size() > 20) return false; for(const auto & c : accountName) diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 1ec3d3299..a1d681d9a 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -250,8 +250,8 @@ void CVCMIServer::prepareToRestart() campaignBonus = si->campState->getBonusID(campaignMap).value_or(-1); } - for(auto c : activeConnections) - c->enterLobbyConnectionMode(); + for(auto activeConnection : activeConnections) + activeConnection->enterLobbyConnectionMode(); gh = nullptr; } @@ -322,8 +322,8 @@ bool CVCMIServer::prepareToStartGame() void CVCMIServer::startGameImmediately() { - for(auto c : activeConnections) - c->enterGameplayConnectionMode(gh->gs); + for(auto activeConnection : activeConnections) + activeConnection->enterGameplayConnectionMode(gh->gs); gh->start(si->mode == EStartMode::LOAD_GAME); setState(EServerState::GAMEPLAY); @@ -364,14 +364,13 @@ void CVCMIServer::handleReceivedPack(std::unique_ptr pack) void CVCMIServer::announcePack(std::unique_ptr pack) { - for(auto c : activeConnections) + for(auto activeConnection : 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 //if(c->uuid == uuid && !dynamic_cast(pack.get())) // continue; - - c->sendPack(pack.get()); + activeConnection->sendPack(pack.get()); } applier->getApplier(CTypeList::getInstance().getTypeID(pack.get()))->applyOnServerAfter(this, pack.get()); @@ -396,14 +395,14 @@ void CVCMIServer::announceTxt(const std::string & txt, const std::string & playe bool CVCMIServer::passHost(int toConnectionId) { - for(auto c : activeConnections) + for(auto activeConnection : activeConnections) { - if(isClientHost(c->connectionID)) + if(isClientHost(activeConnection->connectionID)) continue; - if(c->connectionID != toConnectionId) + if(activeConnection->connectionID != toConnectionId) continue; - hostClientId = c->connectionID; + hostClientId = activeConnection->connectionID; announceTxt(boost::str(boost::format("Pass host to connection %d") % toConnectionId)); return true; } @@ -447,10 +446,10 @@ void CVCMIServer::clientConnected(std::shared_ptr c, std::vector c) +void CVCMIServer::clientDisconnected(std::shared_ptr connection) { - c->getConnection()->close(); - vstd::erase(activeConnections, c); + connection->getConnection()->close(); + vstd::erase(activeConnections, connection); // PlayerReinitInterface startAiPack; // startAiPack.playerConnectionId = PlayerSettings::PLAYER_AI; diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index d312befdd..1802b49b8 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -206,8 +206,8 @@ void ApplyOnServerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) { - for(const auto & c : srv.activeConnections) - c->enterLobbyConnectionMode(); + for(const auto & connection : srv.activeConnections) + connection->enterLobbyConnectionMode(); } void ClientPermissionsCheckerNetPackVisitor::visitLobbyPrepareStartGame(LobbyPrepareStartGame & pack) @@ -250,11 +250,11 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyStartGame(LobbyStartGam srv.startGameImmediately(); else { - for(const auto & c : srv.activeConnections) + for(const auto & connection : srv.activeConnections) { - if(c->connectionID == pack.clientId) + if(connection->connectionID == pack.clientId) { - c->enterGameplayConnectionMode(srv.gh->gameState()); + connection->enterGameplayConnectionMode(srv.gh->gameState()); srv.reconnectPlayer(pack.clientId); } } From ece3403fc7bc088cc70ea0fcf99690bdbcc12a51 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 21:53:38 +0200 Subject: [PATCH 104/250] Fixes demon summoning - sacrificed creatures no longer remain after battle and included into casualties --- server/battles/BattleResultProcessor.cpp | 50 ++++++++++++++---------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index 9a1beabff..39af62295 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -47,26 +47,31 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CBattleInfoCallback & battle, PlayerColor color = battle.sideToPlayer(sideInBattle); - for(const CStack * stConst : battle.battleGetAllStacks(true)) + auto allStacks = battle.battleGetStacksIf([color](const CStack * stack){ + + if (stack->summoned)//don't take into account temporary summoned stacks + return false; + + if(stack->unitOwner() != color) //remove only our stacks + return false; + + if (stack->isTurret()) + return false; + + return true; + }); + + for(const CStack * stConst : allStacks) { // Use const cast - in order to call non-const "takeResurrected" for proper calculation of casualties // TODO: better solution CStack * st = const_cast(stConst); - if(st->summoned) //don't take into account temporary summoned stacks - continue; - if(st->unitOwner() != color) //remove only our stacks - continue; - logGlobal->debug("Calculating casualties for %s", st->nodeName()); st->health.takeResurrected(); - if(st->unitSlot() == SlotID::ARROW_TOWERS_SLOT) - { - logGlobal->debug("Ignored arrow towers stack."); - } - else if(st->unitSlot() == SlotID::WAR_MACHINES_SLOT) + if(st->unitSlot() == SlotID::WAR_MACHINES_SLOT) { auto warMachine = st->unitType()->warMachine; @@ -125,15 +130,9 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CBattleInfoCallback & battle, StackLocation sl(army, st->unitSlot()); newStackCounts.push_back(TStackAndItsNewCount(sl, 0)); } - else if(st->getCount() < army->getStackCount(st->unitSlot())) + else if(st->getCount() != army->getStackCount(st->unitSlot())) { - logGlobal->debug("Stack lost %d units.", army->getStackCount(st->unitSlot()) - st->getCount()); - StackLocation sl(army, st->unitSlot()); - newStackCounts.push_back(TStackAndItsNewCount(sl, st->getCount())); - } - else if(st->getCount() > army->getStackCount(st->unitSlot())) - { - logGlobal->debug("Stack gained %d units.", st->getCount() - army->getStackCount(st->unitSlot())); + logGlobal->debug("Stack size changed: %d -> %d units.", army->getStackCount(st->unitSlot()), st->getCount()); StackLocation sl(army, st->unitSlot()); newStackCounts.push_back(TStackAndItsNewCount(sl, st->getCount())); } @@ -596,7 +595,18 @@ void BattleResultProcessor::setBattleResult(const CBattleInfoCallback & battle, battleResult->result = resultType; battleResult->winner = victoriusSide; //surrendering side loses - for(const auto & st : battle.battleGetAllStacks(true)) //setting casualties + auto allStacks = battle.battleGetStacksIf([](const CStack * stack){ + + if (stack->summoned)//don't take into account temporary summoned stacks + return false; + + if (stack->isTurret()) + return false; + + return true; + }); + + for(const auto & st : allStacks) //setting casualties { si32 killed = st->getKilled(); if(killed > 0) From 678cacbd254710206f1843863310bfec8692f026 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Tue, 13 Feb 2024 15:21:30 +0100 Subject: [PATCH 105/250] Remove more redundant `virtual` specifiers `grep -nr "virtual " | grep -v googletest | grep " override" | grep -v overriden > ../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(":",2) 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/Nullkiller/Behaviors/BuildingBehavior.h | 4 ++-- AI/Nullkiller/Behaviors/BuyArmyBehavior.h | 4 ++-- .../Behaviors/CaptureObjectsBehavior.h | 4 ++-- AI/Nullkiller/Behaviors/ClusterBehavior.h | 2 +- AI/Nullkiller/Behaviors/DefenceBehavior.h | 4 ++-- AI/Nullkiller/Behaviors/GatherArmyBehavior.h | 2 +- AI/Nullkiller/Behaviors/RecruitHeroBehavior.h | 2 +- AI/Nullkiller/Behaviors/StartupBehavior.h | 2 +- AI/Nullkiller/Behaviors/StayAtTownBehavior.h | 2 +- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 18 +++++++++--------- AI/Nullkiller/Goals/BuildThis.h | 2 +- AI/Nullkiller/Goals/BuyArmy.h | 2 +- AI/Nullkiller/Goals/CGoal.h | 2 +- AI/Nullkiller/Goals/CaptureObject.h | 4 ++-- AI/Nullkiller/Goals/CompleteQuest.h | 4 ++-- AI/Nullkiller/Goals/Composition.h | 2 +- AI/Nullkiller/Goals/Invalid.h | 2 +- AI/Nullkiller/Goals/RecruitHero.h | 2 +- AI/Nullkiller/Goals/StayAtTown.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 +- .../AdventureSpellCastMovementActions.h | 2 +- .../Pathfinding/Actions/BattleAction.h | 2 +- .../Pathfinding/Actions/BoatActions.h | 6 +++--- .../Pathfinding/Actions/QuestAction.h | 4 ++-- .../Pathfinding/Actions/TownPortalAction.h | 2 +- AI/Nullkiller/Pathfinding/Actors.h | 6 +++--- AI/VCAI/Pathfinding/AINodeStorage.h | 2 +- AI/VCAI/Pathfinding/Actions/BattleAction.h | 2 +- AI/VCAI/Pathfinding/Actions/BoatActions.h | 4 ++-- AI/VCAI/Pathfinding/Actions/TownPortalAction.h | 2 +- lib/BattleFieldHandler.h | 4 ++-- lib/CCreatureSet.h | 2 +- lib/CGameInfoCallback.h | 2 +- lib/CGameInterface.h | 8 ++++---- lib/RiverHandler.h | 4 ++-- lib/RoadHandler.h | 4 ++-- lib/TerrainHandler.h | 4 ++-- lib/bonuses/Updaters.h | 10 +++++----- lib/pathfinder/NodeStorage.h | 2 +- lib/rmg/CRmgTemplateStorage.h | 4 ++-- 44 files changed, 76 insertions(+), 76 deletions(-) diff --git a/AI/Nullkiller/Behaviors/BuildingBehavior.h b/AI/Nullkiller/Behaviors/BuildingBehavior.h index 46d12dd13..4836d1cb0 100644 --- a/AI/Nullkiller/Behaviors/BuildingBehavior.h +++ b/AI/Nullkiller/Behaviors/BuildingBehavior.h @@ -25,8 +25,8 @@ namespace Goals { } - virtual Goals::TGoalVec decompose() const override; - virtual std::string toString() const override; + Goals::TGoalVec decompose() const override; + std::string toString() 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 2d6ea9f0d..909e7221c 100644 --- a/AI/Nullkiller/Behaviors/BuyArmyBehavior.h +++ b/AI/Nullkiller/Behaviors/BuyArmyBehavior.h @@ -24,8 +24,8 @@ namespace Goals { } - virtual Goals::TGoalVec decompose() const override; - virtual std::string toString() const override; + Goals::TGoalVec decompose() const override; + std::string toString() 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 1e29fb03c..09bdd8e0a 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h @@ -48,8 +48,8 @@ namespace Goals specificObjects = true; } - virtual Goals::TGoalVec decompose() const override; - virtual std::string toString() const override; + Goals::TGoalVec decompose() const override; + std::string toString() const override; CaptureObjectsBehavior & ofType(int type) { diff --git a/AI/Nullkiller/Behaviors/ClusterBehavior.h b/AI/Nullkiller/Behaviors/ClusterBehavior.h index 22c88f2ce..c19642a8d 100644 --- a/AI/Nullkiller/Behaviors/ClusterBehavior.h +++ b/AI/Nullkiller/Behaviors/ClusterBehavior.h @@ -29,7 +29,7 @@ namespace Goals } TGoalVec decompose() const override; - virtual std::string toString() const override; + std::string toString() const override; bool operator==(const ClusterBehavior & other) const override { diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.h b/AI/Nullkiller/Behaviors/DefenceBehavior.h index 1eb4ef682..18d577c66 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.h +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.h @@ -29,8 +29,8 @@ namespace Goals { } - virtual Goals::TGoalVec decompose() const override; - virtual std::string toString() const override; + Goals::TGoalVec decompose() const override; + std::string toString() const override; bool operator==(const DefenceBehavior & other) const override { diff --git a/AI/Nullkiller/Behaviors/GatherArmyBehavior.h b/AI/Nullkiller/Behaviors/GatherArmyBehavior.h index 1ae2f3481..b2ef06113 100644 --- a/AI/Nullkiller/Behaviors/GatherArmyBehavior.h +++ b/AI/Nullkiller/Behaviors/GatherArmyBehavior.h @@ -26,7 +26,7 @@ namespace Goals } TGoalVec decompose() const override; - virtual std::string toString() const override; + std::string toString() const override; bool operator==(const GatherArmyBehavior & other) const override { diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.h b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.h index 103f25d50..e45c16e67 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.h +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.h @@ -26,7 +26,7 @@ namespace Goals } TGoalVec decompose() const override; - virtual std::string toString() const override; + std::string toString() const override; bool operator==(const RecruitHeroBehavior & other) const override { diff --git a/AI/Nullkiller/Behaviors/StartupBehavior.h b/AI/Nullkiller/Behaviors/StartupBehavior.h index 8bfcc5734..0386b60a0 100644 --- a/AI/Nullkiller/Behaviors/StartupBehavior.h +++ b/AI/Nullkiller/Behaviors/StartupBehavior.h @@ -26,7 +26,7 @@ namespace Goals } TGoalVec decompose() const override; - virtual std::string toString() const override; + std::string toString() const override; bool operator==(const StartupBehavior & other) const override { diff --git a/AI/Nullkiller/Behaviors/StayAtTownBehavior.h b/AI/Nullkiller/Behaviors/StayAtTownBehavior.h index dd8363968..6287b85b0 100644 --- a/AI/Nullkiller/Behaviors/StayAtTownBehavior.h +++ b/AI/Nullkiller/Behaviors/StayAtTownBehavior.h @@ -26,7 +26,7 @@ namespace Goals } TGoalVec decompose() const override; - virtual std::string toString() const override; + std::string toString() const override; bool operator==(const StayAtTownBehavior & other) const override { diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 8564f52fc..3540162c3 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -702,7 +702,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG class HeroExchangeEvaluator : public IEvaluationContextBuilder { public: - virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override + void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { if(task->goalType != Goals::HERO_EXCHANGE) return; @@ -719,7 +719,7 @@ public: class ArmyUpgradeEvaluator : public IEvaluationContextBuilder { public: - virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override + void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { if(task->goalType != Goals::ARMY_UPGRADE) return; @@ -736,7 +736,7 @@ public: class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder { public: - virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override + void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { if(task->goalType != Goals::STAY_AT_TOWN) return; @@ -771,7 +771,7 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin class DefendTownEvaluator : public IEvaluationContextBuilder { public: - virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override + void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { if(task->goalType != Goals::DEFEND_TOWN) return; @@ -821,7 +821,7 @@ private: public: ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {} - virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override + void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { if(task->goalType != Goals::EXECUTE_HERO_CHAIN) return; @@ -879,7 +879,7 @@ class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder public: ClusterEvaluationContextBuilder(const Nullkiller * ai) {} - virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override + void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { if(task->goalType != Goals::UNLOCK_CLUSTER) return; @@ -926,7 +926,7 @@ public: class ExchangeSwapTownHeroesContextBuilder : public IEvaluationContextBuilder { public: - virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override + void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { if(task->goalType != Goals::EXCHANGE_SWAP_TOWN_HEROES) return; @@ -954,7 +954,7 @@ private: public: DismissHeroContextBuilder(const Nullkiller * ai) : ai(ai) {} - virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override + void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { if(task->goalType != Goals::DISMISS_HERO) return; @@ -974,7 +974,7 @@ public: class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder { public: - virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override + void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { if(task->goalType != Goals::BUILD_STRUCTURE) return; diff --git a/AI/Nullkiller/Goals/BuildThis.h b/AI/Nullkiller/Goals/BuildThis.h index 0b85108b6..02b93c2ab 100644 --- a/AI/Nullkiller/Goals/BuildThis.h +++ b/AI/Nullkiller/Goals/BuildThis.h @@ -40,7 +40,7 @@ namespace Goals BuildThis(BuildingID Bid, const CGTownInstance * tid); bool operator==(const BuildThis & other) const override; - virtual std::string toString() const override; + 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 bd1080fd9..1de8f57ce 100644 --- a/AI/Nullkiller/Goals/BuyArmy.h +++ b/AI/Nullkiller/Goals/BuyArmy.h @@ -38,7 +38,7 @@ namespace Goals bool operator==(const BuyArmy & other) const override; - virtual std::string toString() const override; + std::string toString() const override; void accept(AIGateway * ai) override; }; diff --git a/AI/Nullkiller/Goals/CGoal.h b/AI/Nullkiller/Goals/CGoal.h index 8aa5b90ad..92e4a92a2 100644 --- a/AI/Nullkiller/Goals/CGoal.h +++ b/AI/Nullkiller/Goals/CGoal.h @@ -92,7 +92,7 @@ namespace Goals bool isElementar() const override { return true; } - virtual HeroPtr getHero() const override { return AbstractGoal::hero; } + HeroPtr getHero() const override { return AbstractGoal::hero; } int getHeroExchangeCount() const override { return 0; } }; diff --git a/AI/Nullkiller/Goals/CaptureObject.h b/AI/Nullkiller/Goals/CaptureObject.h index 5ef3a8d51..2b3c339d6 100644 --- a/AI/Nullkiller/Goals/CaptureObject.h +++ b/AI/Nullkiller/Goals/CaptureObject.h @@ -35,8 +35,8 @@ namespace Goals } bool operator==(const CaptureObject & other) const override; - virtual Goals::TGoalVec decompose() const override; - virtual std::string toString() const override; + Goals::TGoalVec decompose() const override; + std::string toString() 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 e7e0f3386..57b9fb236 100644 --- a/AI/Nullkiller/Goals/CompleteQuest.h +++ b/AI/Nullkiller/Goals/CompleteQuest.h @@ -29,8 +29,8 @@ namespace Goals { } - virtual Goals::TGoalVec decompose() const override; - virtual std::string toString() const override; + Goals::TGoalVec decompose() const override; + std::string toString() const override; bool hasHash() const override { return true; } uint64_t getHash() const override; diff --git a/AI/Nullkiller/Goals/Composition.h b/AI/Nullkiller/Goals/Composition.h index 72bf4e9bb..a45d1327a 100644 --- a/AI/Nullkiller/Goals/Composition.h +++ b/AI/Nullkiller/Goals/Composition.h @@ -27,7 +27,7 @@ namespace Goals } bool operator==(const Composition & other) const override; - virtual std::string toString() const override; + std::string toString() const override; void accept(AIGateway * ai) override; Composition & addNext(const AbstractGoal & goal); Composition & addNext(TSubgoal goal); diff --git a/AI/Nullkiller/Goals/Invalid.h b/AI/Nullkiller/Goals/Invalid.h index 9c02e3091..5447b34b4 100644 --- a/AI/Nullkiller/Goals/Invalid.h +++ b/AI/Nullkiller/Goals/Invalid.h @@ -37,7 +37,7 @@ namespace Goals return true; } - virtual std::string toString() const override + std::string toString() const override { return "Invalid"; } diff --git a/AI/Nullkiller/Goals/RecruitHero.h b/AI/Nullkiller/Goals/RecruitHero.h index 243f1f6d2..101588f19 100644 --- a/AI/Nullkiller/Goals/RecruitHero.h +++ b/AI/Nullkiller/Goals/RecruitHero.h @@ -43,7 +43,7 @@ namespace Goals return true; } - virtual std::string toString() const override; + std::string toString() const override; void accept(AIGateway * ai) override; }; } diff --git a/AI/Nullkiller/Goals/StayAtTown.h b/AI/Nullkiller/Goals/StayAtTown.h index 28aa607a2..9d90037b2 100644 --- a/AI/Nullkiller/Goals/StayAtTown.h +++ b/AI/Nullkiller/Goals/StayAtTown.h @@ -27,7 +27,7 @@ namespace Goals StayAtTown(const CGTownInstance * town, AIPath & path); bool operator==(const StayAtTown & other) const override; - virtual std::string toString() const override; + std::string toString() const override; void accept(AIGateway * ai) override; float getMovementWasted() const { return movementWasted; } }; diff --git a/AI/Nullkiller/Markers/ArmyUpgrade.h b/AI/Nullkiller/Markers/ArmyUpgrade.h index 6f67a3ba4..5ede01e80 100644 --- a/AI/Nullkiller/Markers/ArmyUpgrade.h +++ b/AI/Nullkiller/Markers/ArmyUpgrade.h @@ -30,7 +30,7 @@ namespace Goals ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade); bool operator==(const ArmyUpgrade & other) const override; - virtual std::string toString() const override; + std::string toString() const override; uint64_t getUpgradeValue() const { return upgradeValue; } uint64_t getInitialArmyValue() const { return initialValue; } diff --git a/AI/Nullkiller/Markers/DefendTown.h b/AI/Nullkiller/Markers/DefendTown.h index 30ec60d9d..f03f84036 100644 --- a/AI/Nullkiller/Markers/DefendTown.h +++ b/AI/Nullkiller/Markers/DefendTown.h @@ -31,7 +31,7 @@ namespace Goals DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender); bool operator==(const DefendTown & other) const override; - virtual std::string toString() const override; + 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 fb6be8af9..95546716d 100644 --- a/AI/Nullkiller/Markers/HeroExchange.h +++ b/AI/Nullkiller/Markers/HeroExchange.h @@ -29,7 +29,7 @@ namespace Goals } bool operator==(const HeroExchange & other) const override; - virtual std::string toString() const override; + std::string toString() const override; uint64_t getReinforcementArmyStrength() const; }; diff --git a/AI/Nullkiller/Markers/UnlockCluster.h b/AI/Nullkiller/Markers/UnlockCluster.h index db0ce7658..e504a3318 100644 --- a/AI/Nullkiller/Markers/UnlockCluster.h +++ b/AI/Nullkiller/Markers/UnlockCluster.h @@ -37,7 +37,7 @@ namespace Goals } bool operator==(const UnlockCluster & other) const override; - virtual std::string toString() const override; + 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 a8458939c..c986f080f 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -188,7 +188,7 @@ public: bool selectFirstActor(); bool selectNextActor(); - virtual std::vector getInitialNodes() override; + std::vector getInitialNodes() override; virtual std::vector calculateNeighbours( const PathNodeInfo & source, diff --git a/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h b/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h index 64f48aa0e..7defcebf0 100644 --- a/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h +++ b/AI/Nullkiller/Pathfinding/Actions/AdventureSpellCastMovementActions.h @@ -40,7 +40,7 @@ namespace AIPathfinding bool canAct(const AIPathNode * source) const override; - virtual std::string toString() const override; + std::string toString() const override; }; class WaterWalkingAction : public AdventureCastAction diff --git a/AI/Nullkiller/Pathfinding/Actions/BattleAction.h b/AI/Nullkiller/Pathfinding/Actions/BattleAction.h index d5acb9209..838ba54c2 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BattleAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/BattleAction.h @@ -30,7 +30,7 @@ namespace AIPathfinding void execute(const CGHeroInstance * hero) const override; - virtual std::string toString() const override; + std::string toString() const override; }; } diff --git a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h index cb8d32506..2a4dd67de 100644 --- a/AI/Nullkiller/Pathfinding/Actions/BoatActions.h +++ b/AI/Nullkiller/Pathfinding/Actions/BoatActions.h @@ -38,7 +38,7 @@ namespace AIPathfinding const ChainActor * getActor(const ChainActor * sourceActor) const override; - virtual std::string toString() const override; + std::string toString() const override; private: int32_t getManaCost(const CGHeroInstance * hero) const; @@ -60,11 +60,11 @@ namespace AIPathfinding void execute(const CGHeroInstance * hero) const override; - virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override; + Goals::TSubgoal decompose(const CGHeroInstance * hero) const override; const ChainActor * getActor(const ChainActor * sourceActor) const override; - virtual std::string toString() const override; + std::string toString() const override; const CGObjectInstance * targetObject() const override; }; diff --git a/AI/Nullkiller/Pathfinding/Actions/QuestAction.h b/AI/Nullkiller/Pathfinding/Actions/QuestAction.h index 467932adc..52939e5f7 100644 --- a/AI/Nullkiller/Pathfinding/Actions/QuestAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/QuestAction.h @@ -30,11 +30,11 @@ namespace AIPathfinding bool canAct(const AIPathNode * node) const override; - virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const override; + Goals::TSubgoal decompose(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override; - virtual std::string toString() const override; + std::string toString() const override; }; } diff --git a/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h b/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h index 32795972f..05005156b 100644 --- a/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/TownPortalAction.h @@ -31,7 +31,7 @@ namespace AIPathfinding void execute(const CGHeroInstance * hero) const override; - virtual std::string toString() const override; + std::string toString() const override; }; } diff --git a/AI/Nullkiller/Pathfinding/Actors.h b/AI/Nullkiller/Pathfinding/Actors.h index 2947aa487..4451bda24 100644 --- a/AI/Nullkiller/Pathfinding/Actors.h +++ b/AI/Nullkiller/Pathfinding/Actors.h @@ -136,7 +136,7 @@ private: public: ObjectActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn); - virtual std::string toString() const override; + std::string toString() const override; const CGObjectInstance * getActorObject() const override; }; @@ -154,7 +154,7 @@ private: public: DwellingActor(const CGDwelling * dwelling, uint64_t chainMask, bool waitForGrowth, int dayOfWeek); ~DwellingActor(); - virtual std::string toString() const override; + std::string toString() const override; protected: int getInitialTurn(bool waitForGrowth, int dayOfWeek); @@ -168,7 +168,7 @@ private: public: TownGarrisonActor(const CGTownInstance * town, uint64_t chainMask); - virtual std::string toString() const override; + std::string toString() const override; }; } diff --git a/AI/VCAI/Pathfinding/AINodeStorage.h b/AI/VCAI/Pathfinding/AINodeStorage.h index 4b8118191..518972ed3 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.h +++ b/AI/VCAI/Pathfinding/AINodeStorage.h @@ -87,7 +87,7 @@ public: void initialize(const PathfinderOptions & options, const CGameState * gs) override; - virtual std::vector getInitialNodes() override; + std::vector getInitialNodes() override; virtual std::vector calculateNeighbours( const PathNodeInfo & source, diff --git a/AI/VCAI/Pathfinding/Actions/BattleAction.h b/AI/VCAI/Pathfinding/Actions/BattleAction.h index 630e49a55..dc3adb84e 100644 --- a/AI/VCAI/Pathfinding/Actions/BattleAction.h +++ b/AI/VCAI/Pathfinding/Actions/BattleAction.h @@ -25,6 +25,6 @@ namespace AIPathfinding { } - virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override; + Goals::TSubgoal whatToDo(const HeroPtr & hero) const override; }; } \ No newline at end of file diff --git a/AI/VCAI/Pathfinding/Actions/BoatActions.h b/AI/VCAI/Pathfinding/Actions/BoatActions.h index 114478fcb..793a74486 100644 --- a/AI/VCAI/Pathfinding/Actions/BoatActions.h +++ b/AI/VCAI/Pathfinding/Actions/BoatActions.h @@ -40,7 +40,7 @@ namespace AIPathfinding { } - virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override; + Goals::TSubgoal whatToDo(const HeroPtr & hero) const override; virtual void applyOnDestination( const CGHeroInstance * hero, @@ -66,6 +66,6 @@ namespace AIPathfinding { } - virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override; + Goals::TSubgoal whatToDo(const HeroPtr & hero) const override; }; } diff --git a/AI/VCAI/Pathfinding/Actions/TownPortalAction.h b/AI/VCAI/Pathfinding/Actions/TownPortalAction.h index 8d587e052..eba21d392 100644 --- a/AI/VCAI/Pathfinding/Actions/TownPortalAction.h +++ b/AI/VCAI/Pathfinding/Actions/TownPortalAction.h @@ -27,6 +27,6 @@ namespace AIPathfinding { } - virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override; + Goals::TSubgoal whatToDo(const HeroPtr & hero) const override; }; } diff --git a/lib/BattleFieldHandler.h b/lib/BattleFieldHandler.h index 126b4e3f9..b338cd772 100644 --- a/lib/BattleFieldHandler.h +++ b/lib/BattleFieldHandler.h @@ -70,8 +70,8 @@ public: const std::string & identifier, size_t index) override; - virtual const std::vector & getTypeNames() const override; - virtual std::vector loadLegacyData() override; + const std::vector & getTypeNames() const override; + std::vector loadLegacyData() override; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index d84731c2e..10654b46a 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -127,7 +127,7 @@ public: ArtPlacementMap putArtifact(ArtifactPosition pos, CArtifactInstance * art) override;//from CArtifactSet void removeArtifact(ArtifactPosition pos) override; ArtBearer::ArtBearer bearerType() const override; //from CArtifactSet - virtual std::string nodeName() const override; //from CBonusSystemnode + std::string nodeName() const override; //from CBonusSystemnode void deserializationFix(); PlayerColor getOwner() const override; }; diff --git a/lib/CGameInfoCallback.h b/lib/CGameInfoCallback.h index 2d2065cfc..31aafdde5 100644 --- a/lib/CGameInfoCallback.h +++ b/lib/CGameInfoCallback.h @@ -185,7 +185,7 @@ public: //objects 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; + std::vector getVisitableObjs(int3 pos, bool verbose = true) const override; virtual std::vector getFlaggableObjects(int3 pos) const; virtual const CGObjectInstance * getTopObj (int3 pos) const; virtual PlayerColor getOwner(ObjectInstanceID heroID) const; diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index c3d286455..2bfc0bc9a 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -150,17 +150,17 @@ public: 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; + void battleStacksAttacked(const BattleID & battleID, const std::vector & bsa, bool ranged) 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; + void battleObstaclesChanged(const BattleID & battleID, const std::vector & obstacles) override; + void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector dest, int distance, bool teleport) 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; + void battleUnitsChanged(const BattleID & battleID, const std::vector & units) override; void saveGame(BinarySerializer & h) override; void loadGame(BinaryDeserializer & h) override; diff --git a/lib/RiverHandler.h b/lib/RiverHandler.h index c070a53a8..c13736e76 100644 --- a/lib/RiverHandler.h +++ b/lib/RiverHandler.h @@ -69,8 +69,8 @@ public: RiverTypeHandler(); - virtual const std::vector & getTypeNames() const override; - virtual std::vector loadLegacyData() override; + const std::vector & getTypeNames() const override; + std::vector loadLegacyData() override; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/RoadHandler.h b/lib/RoadHandler.h index f58d5b9bb..5f31d1858 100644 --- a/lib/RoadHandler.h +++ b/lib/RoadHandler.h @@ -59,8 +59,8 @@ public: RoadTypeHandler(); - virtual const std::vector & getTypeNames() const override; - virtual std::vector loadLegacyData() override; + const std::vector & getTypeNames() const override; + std::vector loadLegacyData() override; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/TerrainHandler.h b/lib/TerrainHandler.h index 8b0f68fed..59c6ab62b 100644 --- a/lib/TerrainHandler.h +++ b/lib/TerrainHandler.h @@ -107,8 +107,8 @@ public: const std::string & identifier, size_t index) override; - virtual const std::vector & getTypeNames() const override; - virtual std::vector loadLegacyData() override; + const std::vector & getTypeNames() const override; + std::vector loadLegacyData() override; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/bonuses/Updaters.h b/lib/bonuses/Updaters.h index f96c552e9..8e3a7ed49 100644 --- a/lib/bonuses/Updaters.h +++ b/lib/bonuses/Updaters.h @@ -48,7 +48,7 @@ public: } std::shared_ptr createUpdatedBonus(const std::shared_ptr & b, const CBonusSystemNode & context) const override; - virtual std::string toString() const override; + std::string toString() const override; JsonNode toJsonNode() const override; }; @@ -61,7 +61,7 @@ public: } std::shared_ptr createUpdatedBonus(const std::shared_ptr & b, const CBonusSystemNode & context) const override; - virtual std::string toString() const override; + std::string toString() const override; JsonNode toJsonNode() const override; }; @@ -74,7 +74,7 @@ public: } std::shared_ptr createUpdatedBonus(const std::shared_ptr & b, const CBonusSystemNode & context) const override; - virtual std::string toString() const override; + std::string toString() const override; JsonNode toJsonNode() const override; }; @@ -97,7 +97,7 @@ public: } std::shared_ptr createUpdatedBonus(const std::shared_ptr & b, const CBonusSystemNode & context) const override; - virtual std::string toString() const override; + std::string toString() const override; JsonNode toJsonNode() const override; }; @@ -110,7 +110,7 @@ public: } std::shared_ptr createUpdatedBonus(const std::shared_ptr& b, const CBonusSystemNode& context) const override; - virtual std::string toString() const override; + std::string toString() const override; JsonNode toJsonNode() const override; }; diff --git a/lib/pathfinder/NodeStorage.h b/lib/pathfinder/NodeStorage.h index afe19b10e..e8a70a63b 100644 --- a/lib/pathfinder/NodeStorage.h +++ b/lib/pathfinder/NodeStorage.h @@ -34,7 +34,7 @@ public: void initialize(const PathfinderOptions & options, const CGameState * gs) override; virtual ~NodeStorage() = default; - virtual std::vector getInitialNodes() override; + std::vector getInitialNodes() override; virtual std::vector calculateNeighbours( const PathNodeInfo & source, diff --git a/lib/rmg/CRmgTemplateStorage.h b/lib/rmg/CRmgTemplateStorage.h index 4470c51d8..65b0c0f09 100644 --- a/lib/rmg/CRmgTemplateStorage.h +++ b/lib/rmg/CRmgTemplateStorage.h @@ -27,8 +27,8 @@ public: std::vector loadLegacyData() override; /// loads single object into game. Scope is namespace of this object, same as name of source mod - virtual void loadObject(std::string scope, std::string name, const JsonNode & data) override; - virtual void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override; + void loadObject(std::string scope, std::string name, const JsonNode & data) override; + void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override; void afterLoadFinalization() override; From c65794b9e355bc65103245be4fea2cd39db10afc Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Tue, 13 Feb 2024 15:23:30 +0100 Subject: [PATCH 106/250] Fix typos --- client/gui/CIntObject.h | 2 +- config/filesystem.json | 2 +- docs/modders/Difficulty.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/gui/CIntObject.h b/client/gui/CIntObject.h index 7909e72e7..71bda7911 100644 --- a/client/gui/CIntObject.h +++ b/client/gui/CIntObject.h @@ -108,7 +108,7 @@ public: bool isPopupWindow() const override; /// called only for windows whenever screen size changes - /// default behavior is to re-center, can be overriden + /// default behavior is to re-center, can be overridden void onScreenResize() override; /// returns true if UI elements wants to handle event of specific type (LCLICK, SHOW_POPUP ...) diff --git a/config/filesystem.json b/config/filesystem.json index 0a388c31b..e57599a78 100644 --- a/config/filesystem.json +++ b/config/filesystem.json @@ -28,7 +28,7 @@ {"type" : "snd", "path" : "Data/H3ab_ahd.snd"}, {"type" : "snd", "path" : "Data/Heroes3.snd"}, {"type" : "snd", "path" : "Data/Heroes3-cd2.snd"}, - //WoG have overriden sounds with .82m extension in Data + //WoG have overridden sounds with .82m extension in Data {"type" : "dir", "path" : "Data", "depth": 0} ], "MUSIC/": diff --git a/docs/modders/Difficulty.md b/docs/modders/Difficulty.md index 75c44d768..190df9aa8 100644 --- a/docs/modders/Difficulty.md +++ b/docs/modders/Difficulty.md @@ -4,7 +4,7 @@ Since VCMI 1.4.0 there are more capabilities to configure difficulty parameters. It means, that modders can give different bonuses to AI or human players depending on selected difficulty -Difficulty configuration is located in [config/difficulty.json](../config/difficulty.json) file and can be overriden by mods. +Difficulty configuration is located in [config/difficulty.json](../config/difficulty.json) file and can be overridden by mods. ## Format summary From 77e8d61755d7e9cf96c8ff9dad1c4a72045440d0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 13 Feb 2024 22:20:30 +0200 Subject: [PATCH 107/250] Build lobby on Linux CI --- CMakePresets.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakePresets.json b/CMakePresets.json index 4f2de8e2f..d35b24e74 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -51,7 +51,8 @@ "inherits": "linux-release", "hidden": true, "cacheVariables": { - "ENABLE_TEST": "ON", + "ENABLE_LOBBY": "ON", + "ENABLE_TEST": "ON", "ENABLE_LUA": "ON" } }, From a8f79ad9d806e615c10405a62fce290b2a5d6f17 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Wed, 14 Feb 2024 09:32:34 +0100 Subject: [PATCH 108/250] Try to fix CMake warning: -- Found SDL2: /usr/lib/x86_64-linux-gnu/libSDL2.so (found version "2.0.20") CMake Warning (dev) at /usr/local/share/cmake-3.28/Modules/FindPackageHandleStandardArgs.cmake:438 (message): The package name passed to `find_package_handle_standard_args` (SDL2main) does not match the name of the calling package (SDL2). This can lead to problems in calling code that expects `find_package` result variables (e.g., `_FOUND`) to follow a certain pattern. Call Stack (most recent call first): cmake_modules/FindSDL2.cmake:318 (FIND_PACKAGE_HANDLE_STANDARD_ARGS) CMakeLists.txt:473 (find_package) This warning is for project developers. Use -Wno-dev to suppress it. --- cmake_modules/FindSDL2.cmake | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cmake_modules/FindSDL2.cmake b/cmake_modules/FindSDL2.cmake index c3c537a42..4228a3c0b 100644 --- a/cmake_modules/FindSDL2.cmake +++ b/cmake_modules/FindSDL2.cmake @@ -314,13 +314,6 @@ FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR VERSION_VAR SDL2_VERSION_STRING) -if(SDL2MAIN_LIBRARY) - FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2main - REQUIRED_VARS SDL2MAIN_LIBRARY SDL2_INCLUDE_DIR - VERSION_VAR SDL2_VERSION_STRING) -endif() - - mark_as_advanced(SDL2_PATH SDL2_NO_DEFAULT_PATH SDL2_LIBRARY From 7359b66f99ff7f37df4db12fba4130d51eb71ed7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 13 Feb 2024 23:42:05 +0200 Subject: [PATCH 109/250] Do not use floating point equality checks --- AI/BattleAI/AttackPossibility.cpp | 10 +++------ AI/BattleAI/BattleExchangeVariant.cpp | 2 +- AI/Nullkiller/AIGateway.cpp | 5 +++-- .../Analyzers/DangerHitMapAnalyzer.cpp | 2 +- Global.h | 13 ++++++++++++ client/mapView/MapViewCache.cpp | 4 ++-- client/render/ColorFilter.cpp | 8 +++---- client/widgets/Slider.cpp | 21 ++++++++++--------- lib/CCreatureHandler.cpp | 8 +++---- lib/rmg/float3.h | 17 --------------- 10 files changed, 42 insertions(+), 48 deletions(-) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index af36ed07a..b2d4c769f 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -62,16 +62,12 @@ void DamageCache::buildDamageCache(std::shared_ptr hb, int sid int64_t DamageCache::getDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr hb) { - auto damage = damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount(); + bool wasComputedBefore = damageCache[attacker->unitId()].count(defender->unitId()); - if(damage == 0) - { + if (!wasComputedBefore) cacheDamage(attacker, defender, hb); - damage = damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount(); - } - - return static_cast(damage); + return damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount(); } int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr hb) diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index f0a29682b..d7902f878 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -270,7 +270,7 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget( { float score = evaluateExchange(ap, 0, targets, damageCache, hb); - if(score > result.score || (score == result.score && result.wait)) + if(score > result.score || (vstd::isAlmostEqual(score, result.score) && result.wait)) { result.score = score; result.bestAttack = ap; diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 9ed9fe77f..1fced0f95 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -632,7 +632,8 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vectorid == hero->id ? objects.back() : objects.front(); auto objType = topObj->ID; // top object should be our hero auto goalObjectID = nullkiller->getTargetObject(); - auto ratio = (float)nullkiller->dangerEvaluator->evaluateDanger(target, hero.get()) / (float)hero->getTotalStrength(); + auto danger = nullkiller->dangerEvaluator->evaluateDanger(target, hero.get()); + auto ratio = static_cast(danger) / hero->getTotalStrength(); answer = topObj->id == goalObjectID; // no if we do not aim to visit this object logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name, ratio); @@ -648,7 +649,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector (1 / SAFE_ATTACK_CONSTANT); answer = !dangerUnknown && !dangerTooHigh; diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index ccf6b7ecb..76ac640f7 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -226,7 +226,7 @@ void DangerHitMapAnalyzer::calculateTileOwners() } } - if(ourDistance == enemyDistance) + if(vstd::isAlmostEqual(ourDistance, enemyDistance)) { hitMap[pos.x][pos.y][pos.z].closestTown = nullptr; } diff --git a/Global.h b/Global.h index 7135412b1..43ee6d7ec 100644 --- a/Global.h +++ b/Global.h @@ -685,6 +685,19 @@ namespace vstd return a + (b - a) * f; } + template + bool isAlmostZero(const Floating & value) + { + constexpr Floating epsilon(0.00001); + return std::abs(value) < epsilon; + } + + template + bool isAlmostEqual(const Floating1 & left, const Floating2 & right) + { + return isAlmostZero(left - right); + } + ///compile-time version of std::abs for ints for int3, in clang++15 std::abs is constexpr static constexpr int abs(int i) { if(i < 0) return -i; diff --git a/client/mapView/MapViewCache.cpp b/client/mapView/MapViewCache.cpp index ad851ef11..2bd21ce8d 100644 --- a/client/mapView/MapViewCache.cpp +++ b/client/mapView/MapViewCache.cpp @@ -141,7 +141,7 @@ void MapViewCache::update(const std::shared_ptr & context) void MapViewCache::render(const std::shared_ptr & context, Canvas & target, bool fullRedraw) { bool mapMoved = (cachedPosition != model->getMapViewCenter()); - bool lazyUpdate = !mapMoved && !fullRedraw && context->viewTransitionProgress() == 0; + bool lazyUpdate = !mapMoved && !fullRedraw && vstd::isAlmostZero(context->viewTransitionProgress()); Rect dimensions = model->getTilesTotalRect(); @@ -184,7 +184,7 @@ void MapViewCache::render(const std::shared_ptr & context, } } - if(context->viewTransitionProgress() != 0) + if(!vstd::isAlmostZero(context->viewTransitionProgress())) target.drawTransparent(*terrainTransition, Point(0, 0), 1.0 - context->viewTransitionProgress()); cachedPosition = model->getMapViewCenter(); diff --git a/client/render/ColorFilter.cpp b/client/render/ColorFilter.cpp index d6234116f..4540c0557 100644 --- a/client/render/ColorFilter.cpp +++ b/client/render/ColorFilter.cpp @@ -41,10 +41,10 @@ bool ColorFilter::operator != (const ColorFilter & other) const bool ColorFilter::operator == (const ColorFilter & other) const { return - r.r == other.r.r && r.g && other.r.g && r.b == other.r.b && r.a == other.r.a && - g.r == other.g.r && g.g && other.g.g && g.b == other.g.b && g.a == other.g.a && - b.r == other.b.r && b.g && other.b.g && b.b == other.b.b && b.a == other.b.a && - a == other.a; + vstd::isAlmostEqual(r.r, other.r.r) && vstd::isAlmostEqual(r.g, other.r.g) && vstd::isAlmostEqual(r.b, other.r.b) && vstd::isAlmostEqual(r.a, other.r.a) && + vstd::isAlmostEqual(g.r, other.g.r) && vstd::isAlmostEqual(g.g, other.g.g) && vstd::isAlmostEqual(g.b, other.g.b) && vstd::isAlmostEqual(g.a, other.g.a) && + vstd::isAlmostEqual(b.r, other.b.r) && vstd::isAlmostEqual(b.g, other.b.g) && vstd::isAlmostEqual(b.b, other.b.b) && vstd::isAlmostEqual(b.a, other.b.a) && + vstd::isAlmostEqual(a, other.a); } ColorFilter ColorFilter::genEmptyShifter( ) diff --git a/client/widgets/Slider.cpp b/client/widgets/Slider.cpp index 8a37b6f46..dff5d3229 100644 --- a/client/widgets/Slider.cpp +++ b/client/widgets/Slider.cpp @@ -21,23 +21,24 @@ void CSlider::mouseDragged(const Point & cursorPosition, const Point & lastUpdateDistance) { - double v = 0; + double newPosition = 0; if(getOrientation() == Orientation::HORIZONTAL) { - v = cursorPosition.x - pos.x - 24; - v *= positions; - v /= (pos.w - 48); + newPosition = cursorPosition.x - pos.x - 24; + newPosition *= positions; + newPosition /= (pos.w - 48); } else { - v = cursorPosition.y - pos.y - 24; - v *= positions; - v /= (pos.h - 48); + newPosition = cursorPosition.y - pos.y - 24; + newPosition *= positions; + newPosition /= (pos.h - 48); } - v += 0.5; - if(v!=value) + + int positionInteger = std::round(newPosition); + if(positionInteger != value) { - scrollTo(static_cast(v)); + scrollTo(static_cast(newPosition)); } } diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 87889fe1d..530c3c45b 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -844,8 +844,8 @@ void CCreatureHandler::loadUnitAnimInfo(JsonNode & graphics, CLegacyConfigParser missile["attackClimaxFrame"].Float() = parser.readNumber(); // assume that creature is not a shooter and should not have whole missile field - if (missile["frameAngles"].Vector()[0].Float() == 0 && - missile["attackClimaxFrame"].Float() == 0) + if (missile["frameAngles"].Vector()[0].Integer() == 0 && + missile["attackClimaxFrame"].Integer() == 0) graphics.Struct().erase("missile"); } @@ -987,10 +987,10 @@ void CCreatureHandler::loadStackExperience(CCreature * creature, const JsonNode int lastVal = 0; for (const JsonNode &val : values) { - if (val.Float() != lastVal) + if (val.Integer() != lastVal) { JsonNode bonusInput = exp["bonus"]; - bonusInput["val"].Float() = static_cast(val.Float()) - lastVal; + bonusInput["val"].Float() = val.Integer() - lastVal; auto bonus = JsonUtils::parseBonus (bonusInput); bonus->source = BonusSource::STACK_EXPERIENCE; diff --git a/lib/rmg/float3.h b/lib/rmg/float3.h index 97bbc95fc..ca8775c3a 100644 --- a/lib/rmg/float3.h +++ b/lib/rmg/float3.h @@ -113,23 +113,6 @@ public: return *this; } - bool operator==(const float3 & i) const { return (x == i.x) && (y == i.y) && (z == i.z); } - bool operator!=(const float3 & i) const { return (x != i.x) || (y != i.y) || (z != i.z); } - - bool operator<(const float3 & i) const - { - if (zi.z) - return false; - if (yi.y) - return false; - - return x Date: Tue, 13 Feb 2024 23:42:31 +0200 Subject: [PATCH 110/250] Replace throws() with nothrow --- AI/Nullkiller/Goals/AbstractGoal.h | 12 ++---------- AI/VCAI/VCAI.h | 12 ++---------- lib/rmg/Functions.h | 8 ++------ 3 files changed, 6 insertions(+), 26 deletions(-) diff --git a/AI/Nullkiller/Goals/AbstractGoal.h b/AI/Nullkiller/Goals/AbstractGoal.h index d089083bf..2579c1e12 100644 --- a/AI/Nullkiller/Goals/AbstractGoal.h +++ b/AI/Nullkiller/Goals/AbstractGoal.h @@ -180,11 +180,7 @@ public: { } - virtual ~cannotFulfillGoalException() throw () - { - }; - - const char * what() const throw () override + const char * what() const noexcept override { return msg.c_str(); } @@ -203,11 +199,7 @@ public: msg = goal->toString(); } - virtual ~goalFulfilledException() throw () - { - }; - - const char * what() const throw () override + const char * what() const noexcept override { return msg.c_str(); } diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 0cc43db2c..4db3f89f0 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -371,11 +371,7 @@ public: { } - virtual ~cannotFulfillGoalException() throw () - { - }; - - const char * what() const throw () override + const char * what() const noexcept override { return msg.c_str(); } @@ -394,11 +390,7 @@ public: msg = goal->name(); } - virtual ~goalFulfilledException() throw () - { - }; - - const char * what() const throw () override + const char * what() const noexcept override { return msg.c_str(); } diff --git a/lib/rmg/Functions.h b/lib/rmg/Functions.h index 3a4885b5f..0bcaee0d7 100644 --- a/lib/rmg/Functions.h +++ b/lib/rmg/Functions.h @@ -28,12 +28,8 @@ public: explicit rmgException(const std::string& _Message) : msg(_Message) { } - - virtual ~rmgException() throw () - { - }; - - const char *what() const throw () override + + const char *what() const noexcept override { return msg.c_str(); } From 6d6137accc80532474db77e17dde6aabf4c3faf7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 13 Feb 2024 23:49:00 +0200 Subject: [PATCH 111/250] Simplified code --- lib/gameState/CGameState.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 9fa865db0..545c2b43c 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1383,10 +1383,8 @@ bool CGameState::checkForVictory(const PlayerColor & player, const EventConditio int total = 0; //creature counter for(auto object : map->objects) { - const CArmedInstance *ai = nullptr; - if(object - && object->tempOwner == player //object controlled by player - && (ai = dynamic_cast(object.get()))) //contains army + const CArmedInstance *ai = dynamic_cast(object.get()); + if(ai && ai->getOwner() == player) { for(const auto & elem : ai->Slots()) //iterate through army if(elem.second->getId() == condition.objectType.as()) //it's searched creature From 0d74959a3320d805f0631b9c28f8410bf53643c3 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 14 Feb 2024 12:07:01 +0200 Subject: [PATCH 112/250] Better float comparison --- Global.h | 5 +++-- lib/gameState/CGameState.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Global.h b/Global.h index 43ee6d7ec..7834b9a76 100644 --- a/Global.h +++ b/Global.h @@ -689,13 +689,14 @@ namespace vstd bool isAlmostZero(const Floating & value) { constexpr Floating epsilon(0.00001); - return std::abs(value) < epsilon; + return std::abs(value) <= epsilon; } template bool isAlmostEqual(const Floating1 & left, const Floating2 & right) { - return isAlmostZero(left - right); + const auto relativeEpsilon = std::max(std::abs(left), std::abs(right)) * 0.00001; + return std::abs(left - right) <= relativeEpsilon; } ///compile-time version of std::abs for ints for int3, in clang++15 std::abs is constexpr diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 545c2b43c..ebe7b6710 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1383,7 +1383,7 @@ bool CGameState::checkForVictory(const PlayerColor & player, const EventConditio int total = 0; //creature counter for(auto object : map->objects) { - const CArmedInstance *ai = dynamic_cast(object.get()); + const auto * ai = dynamic_cast(object.get()); if(ai && ai->getOwner() == player) { for(const auto & elem : ai->Slots()) //iterate through army From c23953eac551919cb32a53881fe40354b3cdaad1 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 14 Feb 2024 12:56:37 +0200 Subject: [PATCH 113/250] Remove custom casts --- AI/Nullkiller/AIGateway.cpp | 2 +- .../Pathfinding/Rules/AILayerTransitionRule.cpp | 2 +- AI/VCAI/Goals/CollectRes.cpp | 4 +++- .../Pathfinding/Rules/AILayerTransitionRule.cpp | 2 +- AI/VCAI/VCAI.cpp | 2 +- Global.h | 7 +++++-- client/NetPacksClient.cpp | 6 +++--- client/adventureMap/AdventureMapInterface.cpp | 2 +- lib/mapObjects/IMarket.cpp | 14 -------------- lib/mapObjects/IMarket.h | 2 -- lib/mapObjects/IObjectInterface.cpp | 5 ----- lib/mapObjects/IObjectInterface.h | 2 -- server/CGameHandler.cpp | 2 +- server/NetPacksServer.cpp | 2 +- 14 files changed, 18 insertions(+), 36 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 1fced0f95..0015ed995 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -1407,7 +1407,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade int accquiredResources = 0; if(const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid), false)) { - if(const IMarket * m = IMarket::castFrom(obj, false)) + if(const auto * m = dynamic_cast(obj)) { auto freeRes = cb->getResourceAmount(); //trade only resources which are not reserved for(auto it = ResourceSet::nziterator(freeRes); it.valid(); it++) diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp index a43f8f04e..d71aa9cb0 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -131,7 +131,7 @@ namespace AIPathfinding { if(obj->ID != Obj::TOWN) //towns were handled in the previous loop { - if(const IShipyard * shipyard = IShipyard::castFrom(obj)) + if(const auto * shipyard = dynamic_cast(obj)) shipyards.push_back(shipyard); } } diff --git a/AI/VCAI/Goals/CollectRes.cpp b/AI/VCAI/Goals/CollectRes.cpp index a5600d8df..cb780c5ac 100644 --- a/AI/VCAI/Goals/CollectRes.cpp +++ b/AI/VCAI/Goals/CollectRes.cpp @@ -124,7 +124,9 @@ TSubgoal CollectRes::whatToDoToTrade() ai->retrieveVisitableObjs(visObjs, true); for(const CGObjectInstance * obj : visObjs) { - if(const IMarket * m = IMarket::castFrom(obj, false); m && m->allowsTrade(EMarketMode::RESOURCE_RESOURCE)) + const auto * m = dynamic_cast(obj); + + if(m && m->allowsTrade(EMarketMode::RESOURCE_RESOURCE)) { if(obj->ID == Obj::TOWN) { diff --git a/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp index 9603189fc..4e857df98 100644 --- a/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -58,7 +58,7 @@ namespace AIPathfinding { if(obj->ID != Obj::TOWN) //towns were handled in the previous loop { - if(const IShipyard * shipyard = IShipyard::castFrom(obj)) + if(const auto * shipyard = dynamic_cast(obj)) shipyards.push_back(shipyard); } } diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 18d42a511..7ce4679dd 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -2134,7 +2134,7 @@ void VCAI::tryRealize(Goals::Trade & g) //trade int accquiredResources = 0; if(const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid), false)) { - if(const IMarket * m = IMarket::castFrom(obj, false)) + if(const auto * m = dynamic_cast(obj)) { auto freeRes = ah->freeResources(); //trade only resources which are not reserved for(auto it = ResourceSet::nziterator(freeRes); it.valid(); it++) diff --git a/Global.h b/Global.h index 7834b9a76..3ef1a64e4 100644 --- a/Global.h +++ b/Global.h @@ -695,8 +695,11 @@ namespace vstd template bool isAlmostEqual(const Floating1 & left, const Floating2 & right) { - const auto relativeEpsilon = std::max(std::abs(left), std::abs(right)) * 0.00001; - return std::abs(left - right) <= relativeEpsilon; + using Floating = decltype(left + right); + constexpr Floating epsilon(0.00001); + const Floating relativeEpsilon = std::max(std::abs(left), std::abs(right)) * epsilon; + const Floating value = std::abs(left - right); + return value <= relativeEpsilon; } ///compile-time version of std::abs for ints for int3, in clang++15 std::abs is constexpr diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index d01890f37..ff6c037b1 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -958,7 +958,7 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack) case EOpenWindowMode::SHIPYARD_WINDOW: { assert(pack.queryID == QueryID::NONE); - const IShipyard *sy = IShipyard::castFrom(cl.getObj(ObjectInstanceID(pack.object))); + const auto * sy = dynamic_cast(cl.getObj(ObjectInstanceID(pack.object))); callInterfaceIfPresent(cl, sy->getObject()->getOwner(), &IGameEventsReceiver::showShipyardDialog, sy); } break; @@ -974,7 +974,7 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack) case EOpenWindowMode::UNIVERSITY_WINDOW: { //displays University window (when hero enters University on adventure map) - const IMarket *market = IMarket::castFrom(cl.getObj(ObjectInstanceID(pack.object))); + const auto * market = dynamic_cast(cl.getObj(ObjectInstanceID(pack.object))); const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor)); callInterfaceIfPresent(cl, hero->tempOwner, &IGameEventsReceiver::showUniversityWindow, market, hero, pack.queryID); } @@ -984,7 +984,7 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack) //displays Thieves' Guild window (when hero enters Den of Thieves) const CGObjectInstance *obj = cl.getObj(ObjectInstanceID(pack.object)); const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor)); - const IMarket *market = IMarket::castFrom(obj); + const auto *market = dynamic_cast(obj); callInterfaceIfPresent(cl, cl.getTile(obj->visitablePos())->visitableObjects.back()->tempOwner, &IGameEventsReceiver::showMarketWindow, market, hero, pack.queryID); } break; diff --git a/client/adventureMap/AdventureMapInterface.cpp b/client/adventureMap/AdventureMapInterface.cpp index 4cb1ef6e6..7fd0528c7 100644 --- a/client/adventureMap/AdventureMapInterface.cpp +++ b/client/adventureMap/AdventureMapInterface.cpp @@ -852,7 +852,7 @@ Rect AdventureMapInterface::terrainAreaPixels() const const IShipyard * AdventureMapInterface::ourInaccessibleShipyard(const CGObjectInstance *obj) const { - const IShipyard *ret = IShipyard::castFrom(obj); + const auto *ret = dynamic_cast(obj); if(!ret || obj->tempOwner != currentPlayerID || diff --git a/lib/mapObjects/IMarket.cpp b/lib/mapObjects/IMarket.cpp index 02b6d4631..06fb94f1e 100644 --- a/lib/mapObjects/IMarket.cpp +++ b/lib/mapObjects/IMarket.cpp @@ -154,20 +154,6 @@ std::vector IMarket::availableItemsIds(EMarketMode mode) const return ret; } -const IMarket * IMarket::castFrom(const CGObjectInstance *obj, bool verbose) -{ - auto * imarket = dynamic_cast(obj); - if(verbose && !imarket) - { - logGlobal->error("Cannot cast to IMarket"); - if(obj) - { - logGlobal->error("Object type %s", obj->typeName); - } - } - return imarket; -} - IMarket::IMarket() { } diff --git a/lib/mapObjects/IMarket.h b/lib/mapObjects/IMarket.h index cc20300ab..caf3e4cb4 100644 --- a/lib/mapObjects/IMarket.h +++ b/lib/mapObjects/IMarket.h @@ -29,8 +29,6 @@ public: bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units std::vector availableModes() const; - - static const IMarket *castFrom(const CGObjectInstance *obj, bool verbose = true); }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/IObjectInterface.cpp b/lib/mapObjects/IObjectInterface.cpp index 1c2c15548..e7dac1ca0 100644 --- a/lib/mapObjects/IObjectInterface.cpp +++ b/lib/mapObjects/IObjectInterface.cpp @@ -156,9 +156,4 @@ void IShipyard::getBoatCost(TResources & cost) const cost[EGameResID::GOLD] = 1000; } -const IShipyard * IShipyard::castFrom( const CGObjectInstance *obj ) -{ - return dynamic_cast(obj); -} - VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/IObjectInterface.h b/lib/mapObjects/IObjectInterface.h index 8c5b94b8b..12d2baeb1 100644 --- a/lib/mapObjects/IObjectInterface.h +++ b/lib/mapObjects/IObjectInterface.h @@ -101,8 +101,6 @@ class DLL_LINKAGE IShipyard : public IBoatGenerator { public: virtual void getBoatCost(ResourceSet & cost) const; - - static const IShipyard *castFrom(const CGObjectInstance *obj); }; VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index aaa7793f1..7b7fca060 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3528,7 +3528,7 @@ void CGameHandler::objectVisitEnded(const CObjectVisitQuery & query) bool CGameHandler::buildBoat(ObjectInstanceID objid, PlayerColor playerID) { - const IShipyard *obj = IShipyard::castFrom(getObj(objid)); + const auto *obj = dynamic_cast(getObj(objid)); if (obj->shipyardStatus() != IBoatGenerator::GOOD) { diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index fc5b297fb..6ad658784 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -170,7 +170,7 @@ void ApplyGhNetPackVisitor::visitTradeOnMarketplace(TradeOnMarketplace & pack) { const CGObjectInstance * object = gh.getObj(pack.marketId); const CGHeroInstance * hero = gh.getHero(pack.heroId); - const IMarket * market = IMarket::castFrom(object); + const auto * market = dynamic_cast(object); gh.throwIfWrongPlayer(&pack); From c3957c2c2a69bbf71c3fb76bede17a180d3396cc Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 11 Feb 2024 23:09:01 +0200 Subject: [PATCH 114/250] Moved json files to new directory, split on per-class basis --- client/CMusicHandler.cpp | 1 - client/CPlayerInterface.cpp | 1 - client/gui/InterfaceObjectConfigurable.cpp | 3 +- client/gui/InterfaceObjectConfigurable.h | 2 +- client/mainmenu/CMainMenu.cpp | 1 - client/mainmenu/CMainMenu.h | 2 +- client/render/CAnimation.cpp | 2 +- client/render/ColorFilter.cpp | 2 +- client/render/Colors.cpp | 5 +- client/render/Graphics.cpp | 2 +- client/renderSDL/CBitmapHanFont.cpp | 2 +- client/renderSDL/CTrueTypeFont.cpp | 2 +- client/renderSDL/SDLImage.cpp | 2 +- client/renderSDL/ScreenHandler.cpp | 1 + launcher/jsonutils.cpp | 2 + launcher/jsonutils.h | 3 +- launcher/modManager/cmodlist.cpp | 1 - lib/BasicTypes.cpp | 1 - lib/BattleFieldHandler.cpp | 2 +- lib/CArtHandler.cpp | 2 +- lib/CBonusTypeHandler.cpp | 2 +- lib/CConfigHandler.cpp | 7 +- lib/CConfigHandler.h | 2 +- lib/CCreatureHandler.cpp | 1 + lib/CCreatureHandler.h | 1 - lib/CHeroHandler.cpp | 2 +- lib/CMakeLists.txt | 22 +- lib/CSkillHandler.cpp | 4 +- lib/CStack.h | 1 - lib/CTownHandler.cpp | 2 +- lib/GameSettings.cpp | 2 +- lib/LogicalExpression.h | 3 +- lib/ObstacleHandler.cpp | 2 +- lib/ResourceSet.cpp | 1 - lib/RiverHandler.cpp | 2 +- lib/RoadHandler.cpp | 2 +- lib/ScriptHandler.h | 2 +- lib/TerrainHandler.cpp | 2 +- lib/bonuses/Bonus.cpp | 1 + lib/bonuses/BonusEnum.cpp | 7 +- lib/bonuses/BonusList.cpp | 3 +- lib/bonuses/BonusParams.h | 2 +- lib/bonuses/Limiters.cpp | 1 + lib/bonuses/Updaters.cpp | 3 +- lib/campaign/CampaignState.cpp | 1 - lib/filesystem/AdapterLoaders.cpp | 2 +- lib/filesystem/Filesystem.cpp | 2 +- lib/filesystem/ResourcePath.cpp | 1 - lib/gameState/CGameState.cpp | 1 + lib/json/JsonNode.cpp | 430 +++++++++++ lib/{ => json}/JsonNode.h | 95 +-- lib/json/JsonParser.cpp | 465 ++++++++++++ lib/json/JsonParser.h | 82 +++ lib/{ => json}/JsonRandom.cpp | 31 +- lib/{ => json}/JsonRandom.h | 0 lib/{JsonNode.cpp => json/JsonUtils.cpp} | 452 +----------- lib/json/JsonUtils.h | 104 +++ lib/json/JsonValidator.cpp | 687 ++++++++++++++++++ lib/json/JsonValidator.h | 49 ++ lib/json/JsonWriter.cpp | 144 ++++ lib/json/JsonWriter.h | 35 + .../AObjectTypeHandler.cpp | 3 +- .../AObjectTypeHandler.h | 2 +- .../CBankInstanceConstructor.cpp | 2 +- .../CBankInstanceConstructor.h | 1 + .../CObjectClassesHandler.cpp | 2 +- .../CObjectClassesHandler.h | 3 +- .../CommonConstructors.cpp | 2 +- .../DwellingInstanceConstructor.cpp | 2 +- .../DwellingInstanceConstructor.h | 2 + .../HillFortInstanceConstructor.h | 1 + .../ShipyardInstanceConstructor.h | 1 + lib/mapObjects/CGHeroInstance.cpp | 1 + lib/mapObjects/CObjectHandler.cpp | 2 +- lib/mapObjects/ObjectTemplate.cpp | 1 - lib/mapping/CMapHeader.cpp | 1 + lib/mapping/CMapHeader.h | 2 + lib/mapping/CMapService.cpp | 1 + lib/mapping/MapEditUtils.cpp | 1 - lib/mapping/MapFormatJson.cpp | 4 +- lib/mapping/MapFormatJson.h | 1 - lib/mapping/MapIdentifiersH3M.cpp | 1 - lib/modding/CModHandler.cpp | 1 + lib/modding/CModInfo.h | 2 +- lib/modding/ContentTypeHandler.cpp | 1 + lib/modding/ContentTypeHandler.h | 2 +- lib/modding/IdentifierStorage.cpp | 1 - lib/networkPacks/BattleChanges.h | 2 +- lib/networkPacks/EntityChanges.h | 4 +- lib/rewardable/Info.cpp | 2 +- lib/rewardable/Info.h | 2 +- lib/serializer/JsonDeserializer.cpp | 2 - lib/serializer/JsonSerializeFormat.cpp | 2 - lib/serializer/JsonSerializeFormat.h | 3 +- lib/serializer/JsonSerializer.cpp | 2 - lib/serializer/JsonUpdater.cpp | 3 +- lib/spells/CSpellHandler.cpp | 2 +- lib/spells/CSpellHandler.h | 2 +- lib/spells/TargetCondition.cpp | 2 +- lib/spells/effects/Moat.cpp | 1 + lib/spells/effects/Timed.cpp | 1 + mapeditor/Animation.cpp | 2 +- mapeditor/Animation.h | 5 +- mapeditor/graphics.cpp | 1 - mapeditor/jsonutils.cpp | 2 + mapeditor/jsonutils.h | 3 +- mapeditor/maphandler.cpp | 1 - .../resourceExtractor/ResourceConverter.cpp | 1 - scripting/lua/LuaScriptingContext.cpp | 2 +- scripting/lua/LuaSpellEffect.cpp | 1 + scripting/lua/LuaStack.cpp | 2 +- scripting/lua/api/BonusSystem.cpp | 2 +- test/JsonComparer.h | 2 +- test/entity/CCreatureTest.cpp | 1 + test/game/CGameStateTest.cpp | 1 + test/map/CMapEditManagerTest.cpp | 1 - test/map/CMapFormatTest.cpp | 2 - test/mock/BattleFake.h | 1 - test/mock/mock_scripting_Context.h | 2 +- test/scripting/LuaSpellEffectAPITest.cpp | 1 + test/scripting/LuaSpellEffectTest.cpp | 1 + test/scripting/ScriptFixture.h | 2 +- test/spells/effects/CloneTest.cpp | 1 + test/spells/effects/DamageTest.cpp | 1 + test/spells/effects/DispelTest.cpp | 1 + test/spells/effects/EffectFixture.h | 1 - test/spells/effects/HealTest.cpp | 1 + test/spells/effects/SacrificeTest.cpp | 1 + test/spells/effects/SummonTest.cpp | 1 + test/spells/effects/TeleportTest.cpp | 1 + test/spells/effects/TimedTest.cpp | 3 +- 131 files changed, 2164 insertions(+), 653 deletions(-) create mode 100644 lib/json/JsonNode.cpp rename lib/{ => json}/JsonNode.h (56%) create mode 100644 lib/json/JsonParser.cpp create mode 100644 lib/json/JsonParser.h rename lib/{ => json}/JsonRandom.cpp (97%) rename lib/{ => json}/JsonRandom.h (100%) rename lib/{JsonNode.cpp => json/JsonUtils.cpp} (77%) create mode 100644 lib/json/JsonUtils.h create mode 100644 lib/json/JsonValidator.cpp create mode 100644 lib/json/JsonValidator.h create mode 100644 lib/json/JsonWriter.cpp create mode 100644 lib/json/JsonWriter.h diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index 1339a4de9..bb8cf46ee 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -17,7 +17,6 @@ #include "eventsSDL/InputHandler.h" #include "gui/CGuiHandler.h" -#include "../lib/JsonNode.h" #include "../lib/GameConstants.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/constants/StringConstants.h" diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 3d08ad7d9..0a5a548d1 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -75,7 +75,6 @@ #include "../lib/CTownHandler.h" #include "../lib/CondSh.h" #include "../lib/GameConstants.h" -#include "../lib/JsonNode.h" #include "../lib/RoadHandler.h" #include "../lib/StartInfo.h" #include "../lib/TerrainHandler.h" diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index b12bb75b1..38ad05cad 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -29,7 +29,8 @@ #include "../windows/GUIClasses.h" #include "../windows/InfoWindows.h" -#include "../../lib//constants/StringConstants.h" +#include "../../lib/constants/StringConstants.h" +#include "../../lib/json/JsonUtils.h" #include "../../lib/CGeneralTextHandler.h" #include "../../lib/filesystem/ResourcePath.h" diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h index cc812299e..b1ce32b67 100644 --- a/client/gui/InterfaceObjectConfigurable.h +++ b/client/gui/InterfaceObjectConfigurable.h @@ -14,7 +14,7 @@ #include "TextAlignment.h" #include "../render/EFont.h" -#include "../../lib/JsonNode.h" +#include "../../lib/json/JsonNode.h" class CPicture; class CLabel; diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 613812f1a..f36e0718e 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -45,7 +45,6 @@ #include "../../CCallback.h" #include "../../lib/CGeneralTextHandler.h" -#include "../../lib/JsonNode.h" #include "../../lib/campaign/CampaignHandler.h" #include "../../lib/serializer/CTypeList.h" #include "../../lib/filesystem/Filesystem.h" diff --git a/client/mainmenu/CMainMenu.h b/client/mainmenu/CMainMenu.h index 32576d85b..e4d62aef1 100644 --- a/client/mainmenu/CMainMenu.h +++ b/client/mainmenu/CMainMenu.h @@ -10,7 +10,7 @@ #pragma once #include "../windows/CWindowObject.h" -#include "../../lib/JsonNode.h" +#include "../../lib/json/JsonNode.h" #include "../../lib/LoadProgress.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/client/render/CAnimation.cpp b/client/render/CAnimation.cpp index 662f41ccc..91ca64c0f 100644 --- a/client/render/CAnimation.cpp +++ b/client/render/CAnimation.cpp @@ -14,7 +14,7 @@ #include "Graphics.h" #include "../../lib/filesystem/Filesystem.h" -#include "../../lib/JsonNode.h" +#include "../../lib/json/JsonUtils.h" #include "../renderSDL/SDLImage.h" std::shared_ptr CAnimation::getFromExtraDef(std::string filename) diff --git a/client/render/ColorFilter.cpp b/client/render/ColorFilter.cpp index d6234116f..793c1b647 100644 --- a/client/render/ColorFilter.cpp +++ b/client/render/ColorFilter.cpp @@ -10,8 +10,8 @@ #include "StdInc.h" #include "ColorFilter.h" -#include "../../lib/JsonNode.h" #include "../../lib/Color.h" +#include "../../lib/json/JsonNode.h" ColorRGBA ColorFilter::shiftColor(const ColorRGBA & in) const { diff --git a/client/render/Colors.cpp b/client/render/Colors.cpp index e53f55543..d0bd8e23f 100644 --- a/client/render/Colors.cpp +++ b/client/render/Colors.cpp @@ -10,7 +10,8 @@ #include "StdInc.h" #include "Colors.h" -#include "../../lib/JsonNode.h" + +#include "../../lib/json/JsonNode.h" const ColorRGBA Colors::YELLOW = { 229, 215, 123, ColorRGBA::ALPHA_OPAQUE }; const ColorRGBA Colors::WHITE = { 255, 243, 222, ColorRGBA::ALPHA_OPAQUE }; @@ -48,4 +49,4 @@ std::optional Colors::parseColor(std::string text) } return std::nullopt; -} \ No newline at end of file +} diff --git a/client/render/Graphics.cpp b/client/render/Graphics.cpp index 603972feb..023a6ce55 100644 --- a/client/render/Graphics.cpp +++ b/client/render/Graphics.cpp @@ -29,13 +29,13 @@ #include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/CBinaryReader.h" +#include "../../lib/json/JsonNode.h" #include "../lib/modding/CModHandler.h" #include "../lib/modding/ModScope.h" #include "CGameInfo.h" #include "../lib/VCMI_Lib.h" #include "../CCallback.h" #include "../lib/CGeneralTextHandler.h" -#include "../lib/JsonNode.h" #include "../lib/vcmi_endian.h" #include "../lib/CStopWatch.h" #include "../lib/CHeroHandler.h" diff --git a/client/renderSDL/CBitmapHanFont.cpp b/client/renderSDL/CBitmapHanFont.cpp index cb527d054..ac1684185 100644 --- a/client/renderSDL/CBitmapHanFont.cpp +++ b/client/renderSDL/CBitmapHanFont.cpp @@ -13,8 +13,8 @@ #include "CBitmapFont.h" #include "SDL_Extensions.h" -#include "../../lib/JsonNode.h" #include "../../lib/filesystem/Filesystem.h" +#include "../../lib/json/JsonNode.h" #include "../../lib/TextOperations.h" #include "../../lib/Rect.h" diff --git a/client/renderSDL/CTrueTypeFont.cpp b/client/renderSDL/CTrueTypeFont.cpp index b9d19aa12..e977a6c4c 100644 --- a/client/renderSDL/CTrueTypeFont.cpp +++ b/client/renderSDL/CTrueTypeFont.cpp @@ -15,8 +15,8 @@ #include "../render/Colors.h" #include "../renderSDL/SDL_Extensions.h" -#include "../../lib/JsonNode.h" #include "../../lib/TextOperations.h" +#include "../../lib/json/JsonNode.h" #include "../../lib/filesystem/Filesystem.h" #include diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 84373ad46..465597b63 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -18,7 +18,7 @@ #include "../render/CDefFile.h" #include "../render/Graphics.h" -#include "../../lib/JsonNode.h" +#include "../../lib/json/JsonNode.h" #include diff --git a/client/renderSDL/ScreenHandler.cpp b/client/renderSDL/ScreenHandler.cpp index 265c3b021..f8ceb26f1 100644 --- a/client/renderSDL/ScreenHandler.cpp +++ b/client/renderSDL/ScreenHandler.cpp @@ -12,6 +12,7 @@ #include "ScreenHandler.h" #include "../../lib/CConfigHandler.h" +#include "../../lib/constants/StringConstants.h" #include "../gui/CGuiHandler.h" #include "../eventsSDL/NotificationHandler.h" #include "../gui/WindowHandler.h" diff --git a/launcher/jsonutils.cpp b/launcher/jsonutils.cpp index 895eee540..0f472f096 100644 --- a/launcher/jsonutils.cpp +++ b/launcher/jsonutils.cpp @@ -10,6 +10,8 @@ #include "StdInc.h" #include "jsonutils.h" +#include "../lib/json/JsonNode.h" + static QVariantMap JsonToMap(const JsonMap & json) { QVariantMap map; diff --git a/launcher/jsonutils.h b/launcher/jsonutils.h index 6dd7e7bbf..791711eb0 100644 --- a/launcher/jsonutils.h +++ b/launcher/jsonutils.h @@ -10,10 +10,11 @@ #pragma once #include -#include "../lib/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN +class JsonNode; + namespace JsonUtils { QVariant toVariant(const JsonNode & node); diff --git a/launcher/modManager/cmodlist.cpp b/launcher/modManager/cmodlist.cpp index 6facca796..e1a516e27 100644 --- a/launcher/modManager/cmodlist.cpp +++ b/launcher/modManager/cmodlist.cpp @@ -11,7 +11,6 @@ #include "cmodlist.h" #include "../lib/CConfigHandler.h" -#include "../../lib/JsonNode.h" #include "../../lib/filesystem/CFileInputStream.h" #include "../../lib/GameConstants.h" #include "../../lib/modding/CModVersion.h" diff --git a/lib/BasicTypes.cpp b/lib/BasicTypes.cpp index 5ff9a1fe0..b366bda55 100644 --- a/lib/BasicTypes.cpp +++ b/lib/BasicTypes.cpp @@ -13,7 +13,6 @@ #include "VCMI_Lib.h" #include "GameConstants.h" #include "GameSettings.h" -#include "JsonNode.h" #include "bonuses/BonusList.h" #include "bonuses/Bonus.h" #include "bonuses/IBonusBearer.h" diff --git a/lib/BattleFieldHandler.cpp b/lib/BattleFieldHandler.cpp index c0791c568..672e7cf12 100644 --- a/lib/BattleFieldHandler.cpp +++ b/lib/BattleFieldHandler.cpp @@ -11,7 +11,7 @@ #include #include "BattleFieldHandler.h" -#include "JsonNode.h" +#include "json/JsonUtils.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index b932542fc..bbe046d43 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -15,7 +15,7 @@ #include "GameSettings.h" #include "mapObjects/MapObjects.h" #include "constants/StringConstants.h" - +#include "json/JsonUtils.h" #include "mapObjectConstructors/AObjectTypeHandler.h" #include "mapObjectConstructors/CObjectClassesHandler.h" #include "serializer/JsonSerializeFormat.h" diff --git a/lib/CBonusTypeHandler.cpp b/lib/CBonusTypeHandler.cpp index ba86a2d8e..c77a66c3b 100644 --- a/lib/CBonusTypeHandler.cpp +++ b/lib/CBonusTypeHandler.cpp @@ -13,12 +13,12 @@ #include "CBonusTypeHandler.h" -#include "JsonNode.h" #include "filesystem/Filesystem.h" #include "GameConstants.h" #include "CCreatureHandler.h" #include "CGeneralTextHandler.h" +#include "json/JsonUtils.h" #include "spells/CSpellHandler.h" template class std::vector; diff --git a/lib/CConfigHandler.cpp b/lib/CConfigHandler.cpp index 5df984740..5b7924edc 100644 --- a/lib/CConfigHandler.cpp +++ b/lib/CConfigHandler.cpp @@ -10,9 +10,10 @@ #include "StdInc.h" #include "CConfigHandler.h" -#include "../lib/filesystem/Filesystem.h" -#include "../lib/GameConstants.h" -#include "../lib/VCMIDirs.h" +#include "filesystem/Filesystem.h" +#include "GameConstants.h" +#include "VCMIDirs.h" +#include "json/JsonUtils.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CConfigHandler.h b/lib/CConfigHandler.h index a446d8e5c..915863a52 100644 --- a/lib/CConfigHandler.h +++ b/lib/CConfigHandler.h @@ -9,7 +9,7 @@ */ #pragma once -#include "../lib/JsonNode.h" +#include "json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 87889fe1d..8fe85a7a1 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -20,6 +20,7 @@ #include "constants/StringConstants.h" #include "bonuses/Limiters.h" #include "bonuses/Updaters.h" +#include "json/JsonUtils.h" #include "serializer/JsonDeserializer.h" #include "serializer/JsonUpdater.h" #include "mapObjectConstructors/AObjectTypeHandler.h" diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 4bfada15d..b38652ba4 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -14,7 +14,6 @@ #include "ConstTransitivePtr.h" #include "ResourceSet.h" #include "GameConstants.h" -#include "JsonNode.h" #include "IHandlerBase.h" #include "Color.h" #include "filesystem/ResourcePath.h" diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index b8ba71690..413d10fe6 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -13,7 +13,6 @@ #include "CGeneralTextHandler.h" #include "filesystem/Filesystem.h" #include "VCMI_Lib.h" -#include "JsonNode.h" #include "constants/StringConstants.h" #include "battle/BattleHex.h" #include "CCreatureHandler.h" @@ -24,6 +23,7 @@ #include "BattleFieldHandler.h" #include "bonuses/Limiters.h" #include "bonuses/Updaters.h" +#include "json/JsonUtils.h" #include "mapObjectConstructors/AObjectTypeHandler.h" #include "mapObjectConstructors/CObjectClassesHandler.h" #include "modding/IdentifierStorage.h" diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 7cfd624a6..a74e3d195 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -61,6 +61,13 @@ set(lib_SRCS filesystem/MinizipExtensions.cpp filesystem/ResourcePath.cpp + json/JsonNode.cpp + json/JsonParser.cpp + json/JsonRandom.cpp + json/JsonUtils.cpp + json/JsonValidator.cpp + json/JsonWriter.cpp + gameState/CGameState.cpp gameState/CGameStateCampaign.cpp gameState/InfoAboutArmy.cpp @@ -245,9 +252,6 @@ set(lib_SRCS GameSettings.cpp IGameCallback.cpp IHandlerBase.cpp - JsonDetail.cpp - JsonNode.cpp - JsonRandom.cpp LoadProgress.cpp LogicalExpression.cpp MetaString.cpp @@ -399,6 +403,13 @@ set(lib_HEADERS filesystem/MinizipExtensions.h filesystem/ResourcePath.h + json/JsonNode.h + json/JsonParser.h + json/JsonRandom.h + json/JsonUtils.h + json/JsonValidator.h + json/JsonWriter.h + gameState/CGameState.h gameState/CGameStateCampaign.h gameState/EVictoryLossCheckResult.h @@ -636,9 +647,6 @@ set(lib_HEADERS IGameEventsReceiver.h IHandlerBase.h int3.h - JsonDetail.h - JsonNode.h - JsonRandom.h Languages.h LoadProgress.h LogicalExpression.h @@ -772,4 +780,4 @@ if(APPLE_IOS AND NOT USING_CONAN) endif() install(${INSTALL_TYPE} ${LINKED_LIB_REAL} LIBRARY DESTINATION ${LIB_DIR}) endforeach() -endif() \ No newline at end of file +endif() diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index 1c186e009..f6fa25fe9 100644 --- a/lib/CSkillHandler.cpp +++ b/lib/CSkillHandler.cpp @@ -16,12 +16,10 @@ #include "CGeneralTextHandler.h" #include "filesystem/Filesystem.h" +#include "json/JsonUtils.h" #include "modding/IdentifierStorage.h" #include "modding/ModUtility.h" #include "modding/ModScope.h" - -#include "JsonNode.h" - #include "constants/StringConstants.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CStack.h b/lib/CStack.h index 915f8de5a..3252a722a 100644 --- a/lib/CStack.h +++ b/lib/CStack.h @@ -9,7 +9,6 @@ */ #pragma once -#include "JsonNode.h" #include "bonuses/Bonus.h" #include "bonuses/CBonusSystemNode.h" #include "CCreatureHandler.h" //todo: remove diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index cac5982bc..a540a7c71 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -12,7 +12,6 @@ #include "VCMI_Lib.h" #include "CGeneralTextHandler.h" -#include "JsonNode.h" #include "constants/StringConstants.h" #include "CCreatureHandler.h" #include "CHeroHandler.h" @@ -23,6 +22,7 @@ #include "filesystem/Filesystem.h" #include "bonuses/Bonus.h" #include "bonuses/Propagators.h" +#include "json/JsonUtils.h" #include "ResourceSet.h" #include "mapObjectConstructors/AObjectTypeHandler.h" #include "mapObjectConstructors/CObjectClassesHandler.h" diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 3bfa34b2b..6f59f69bc 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -9,7 +9,7 @@ */ #include "StdInc.h" #include "GameSettings.h" -#include "JsonNode.h" +#include "json/JsonUtils.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/LogicalExpression.h b/lib/LogicalExpression.h index 10cf000cb..673662cef 100644 --- a/lib/LogicalExpression.h +++ b/lib/LogicalExpression.h @@ -9,8 +9,7 @@ */ #pragma once -//FIXME: move some of code into .cpp to avoid this include? -#include "JsonNode.h" +#include "json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/ObstacleHandler.cpp b/lib/ObstacleHandler.cpp index f85eae74c..d7b613959 100644 --- a/lib/ObstacleHandler.cpp +++ b/lib/ObstacleHandler.cpp @@ -10,8 +10,8 @@ #include "StdInc.h" #include "ObstacleHandler.h" #include "BattleFieldHandler.h" +#include "json/JsonNode.h" #include "modding/IdentifierStorage.h" -#include "JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/ResourceSet.cpp b/lib/ResourceSet.cpp index be8986312..71db0db46 100644 --- a/lib/ResourceSet.cpp +++ b/lib/ResourceSet.cpp @@ -12,7 +12,6 @@ #include "GameConstants.h" #include "ResourceSet.h" #include "constants/StringConstants.h" -#include "JsonNode.h" #include "serializer/JsonSerializeFormat.h" #include "mapObjects/CObjectHandler.h" #include "VCMI_Lib.h" diff --git a/lib/RiverHandler.cpp b/lib/RiverHandler.cpp index ca0a28756..7ed62a7a5 100644 --- a/lib/RiverHandler.cpp +++ b/lib/RiverHandler.cpp @@ -12,7 +12,7 @@ #include "RiverHandler.h" #include "CGeneralTextHandler.h" #include "GameSettings.h" -#include "JsonNode.h" +#include "json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/RoadHandler.cpp b/lib/RoadHandler.cpp index 0ae298dfb..5ebbe54f1 100644 --- a/lib/RoadHandler.cpp +++ b/lib/RoadHandler.cpp @@ -12,7 +12,7 @@ #include "RoadHandler.h" #include "CGeneralTextHandler.h" #include "GameSettings.h" -#include "JsonNode.h" +#include "json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/ScriptHandler.h b/lib/ScriptHandler.h index a29688ada..57f84b0f8 100644 --- a/lib/ScriptHandler.h +++ b/lib/ScriptHandler.h @@ -13,7 +13,7 @@ #if SCRIPTING_ENABLED #include #include "IHandlerBase.h" -#include "JsonNode.h" +#include "json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/TerrainHandler.cpp b/lib/TerrainHandler.cpp index 805f41362..6980d98b8 100644 --- a/lib/TerrainHandler.cpp +++ b/lib/TerrainHandler.cpp @@ -12,7 +12,7 @@ #include "TerrainHandler.h" #include "CGeneralTextHandler.h" #include "GameSettings.h" -#include "JsonNode.h" +#include "json/JsonNode.h" #include "modding/IdentifierStorage.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/bonuses/Bonus.cpp b/lib/bonuses/Bonus.cpp index c0324824c..6f7a04d3e 100644 --- a/lib/bonuses/Bonus.cpp +++ b/lib/bonuses/Bonus.cpp @@ -26,6 +26,7 @@ #include "../TerrainHandler.h" #include "../constants/StringConstants.h" #include "../battle/BattleInfo.h" +#include "../json/JsonUtils.h" #include "../modding/ModUtility.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/bonuses/BonusEnum.cpp b/lib/bonuses/BonusEnum.cpp index 903e3a32f..ed5fe7c59 100644 --- a/lib/bonuses/BonusEnum.cpp +++ b/lib/bonuses/BonusEnum.cpp @@ -7,13 +7,10 @@ * Full text of license available in license.txt file, in main folder * */ - - #include "StdInc.h" #include "BonusEnum.h" - -#include "../JsonNode.h" +#include "../json/JsonUtils.h" VCMI_LIB_NAMESPACE_BEGIN @@ -82,4 +79,4 @@ namespace BonusDuration } } -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END diff --git a/lib/bonuses/BonusList.cpp b/lib/bonuses/BonusList.cpp index 4920881b3..3fbabefe3 100644 --- a/lib/bonuses/BonusList.cpp +++ b/lib/bonuses/BonusList.cpp @@ -10,8 +10,7 @@ #include "StdInc.h" #include "CBonusSystemNode.h" - -#include "../JsonNode.h" +#include "../json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/bonuses/BonusParams.h b/lib/bonuses/BonusParams.h index b0b2d3ef2..57e79c43a 100644 --- a/lib/bonuses/BonusParams.h +++ b/lib/bonuses/BonusParams.h @@ -12,7 +12,7 @@ #include "Bonus.h" #include "../GameConstants.h" -#include "../JsonNode.h" +#include "../json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index b0e031368..2e952a56f 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -24,6 +24,7 @@ #include "../TerrainHandler.h" #include "../constants/StringConstants.h" #include "../battle/BattleInfo.h" +#include "../json/JsonUtils.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/bonuses/Updaters.cpp b/lib/bonuses/Updaters.cpp index 0debdaabc..5729574c0 100644 --- a/lib/bonuses/Updaters.cpp +++ b/lib/bonuses/Updaters.cpp @@ -13,6 +13,7 @@ #include "Updaters.h" #include "Limiters.h" +#include "../json/JsonUtils.h" #include "../mapObjects/CGHeroInstance.h" #include "../CStack.h" @@ -208,4 +209,4 @@ std::shared_ptr OwnerUpdater::createUpdatedBonus(const std::shared_ptr +Node & resolvePointer(Node & in, const std::string & pointer) +{ + if(pointer.empty()) + return in; + assert(pointer[0] == '/'); + + size_t splitPos = pointer.find('/', 1); + + std::string entry = pointer.substr(1, splitPos - 1); + std::string remainer = splitPos == std::string::npos ? "" : pointer.substr(splitPos); + + if(in.getType() == VCMI_LIB_WRAP_NAMESPACE(JsonNode)::JsonType::DATA_VECTOR) + { + if(entry.find_first_not_of("0123456789") != std::string::npos) // non-numbers in string + throw std::runtime_error("Invalid Json pointer"); + + if(entry.size() > 1 && entry[0] == '0') // leading zeros are not allowed + throw std::runtime_error("Invalid Json pointer"); + + auto index = boost::lexical_cast(entry); + + if (in.Vector().size() > index) + return in.Vector()[index].resolvePointer(remainer); + } + return in[entry].resolvePointer(remainer); +} +} + +VCMI_LIB_NAMESPACE_BEGIN + +using namespace JsonDetail; + +class LibClasses; +class CModHandler; + +static const JsonNode nullNode; + +JsonNode::JsonNode(JsonType Type) +{ + setType(Type); +} + +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); + *this = parser.parse(""); +} + +JsonNode::JsonNode(const JsonPath & fileURI) +{ + auto file = CResourceHandler::get()->load(fileURI)->readAll(); + + JsonParser parser(reinterpret_cast(file.first.get()), file.second); + *this = parser.parse(fileURI.getName()); +} + +JsonNode::JsonNode(const std::string & idx, const JsonPath & fileURI) +{ + auto file = CResourceHandler::get(idx)->load(fileURI)->readAll(); + + JsonParser parser(reinterpret_cast(file.first.get()), file.second); + *this = parser.parse(fileURI.getName()); +} + +JsonNode::JsonNode(const JsonPath & fileURI, bool &isValidSyntax) +{ + auto file = CResourceHandler::get()->load(fileURI)->readAll(); + + JsonParser parser(reinterpret_cast(file.first.get()), file.second); + *this = parser.parse(fileURI.getName()); + isValidSyntax = parser.isValid(); +} + +bool JsonNode::operator == (const JsonNode &other) const +{ + return data == other.data; +} + +bool JsonNode::operator != (const JsonNode &other) const +{ + return !(*this == other); +} + +JsonNode::JsonType JsonNode::getType() const +{ + return static_cast(data.index()); +} + +void JsonNode::setMeta(const std::string & metadata, bool recursive) +{ + meta = metadata; + if (recursive) + { + switch (getType()) + { + break; case JsonType::DATA_VECTOR: + { + for(auto & node : Vector()) + { + node.setMeta(metadata); + } + } + break; case JsonType::DATA_STRUCT: + { + for(auto & node : Struct()) + { + node.second.setMeta(metadata); + } + } + } + } +} + +void JsonNode::setType(JsonType Type) +{ + if (getType() == Type) + return; + + //float<->int conversion + if(getType() == JsonType::DATA_FLOAT && Type == JsonType::DATA_INTEGER) + { + si64 converted = static_cast(std::get(data)); + data = JsonData(converted); + return; + } + else if(getType() == JsonType::DATA_INTEGER && Type == JsonType::DATA_FLOAT) + { + double converted = static_cast(std::get(data)); + data = JsonData(converted); + return; + } + + //Set new node type + switch(Type) + { + break; case JsonType::DATA_NULL: data = JsonData(); + break; case JsonType::DATA_BOOL: data = JsonData(false); + break; case JsonType::DATA_FLOAT: data = JsonData(static_cast(0.0)); + break; case JsonType::DATA_STRING: data = JsonData(std::string()); + break; case JsonType::DATA_VECTOR: data = JsonData(JsonVector()); + break; case JsonType::DATA_STRUCT: data = JsonData(JsonMap()); + break; case JsonType::DATA_INTEGER: data = JsonData(static_cast(0)); + } +} + +bool JsonNode::isNull() const +{ + return getType() == JsonType::DATA_NULL; +} + +bool JsonNode::isNumber() const +{ + return getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT; +} + +bool JsonNode::isString() const +{ + return getType() == JsonType::DATA_STRING; +} + +bool JsonNode::isVector() const +{ + return getType() == JsonType::DATA_VECTOR; +} + +bool JsonNode::isStruct() const +{ + return getType() == JsonType::DATA_STRUCT; +} + +bool JsonNode::containsBaseData() const +{ + switch(getType()) + { + case JsonType::DATA_NULL: + return false; + case JsonType::DATA_STRUCT: + for(const auto & elem : Struct()) + { + if(elem.second.containsBaseData()) + return true; + } + return false; + default: + //other types (including vector) cannot be extended via merge + return true; + } +} + +bool JsonNode::isCompact() const +{ + switch(getType()) + { + case JsonType::DATA_VECTOR: + for(const JsonNode & elem : Vector()) + { + if(!elem.isCompact()) + return false; + } + return true; + case JsonType::DATA_STRUCT: + { + auto propertyCount = Struct().size(); + if(propertyCount == 0) + return true; + else if(propertyCount == 1) + return Struct().begin()->second.isCompact(); + } + return false; + default: + return true; + } +} + +bool JsonNode::TryBoolFromString(bool & success) const +{ + success = true; + if(getType() == JsonNode::JsonType::DATA_BOOL) + return Bool(); + + success = getType() == JsonNode::JsonType::DATA_STRING; + if(success) + { + auto boolParamStr = String(); + boost::algorithm::trim(boolParamStr); + boost::algorithm::to_lower(boolParamStr); + success = boolParamStr == "true"; + + if(success) + return true; + + success = boolParamStr == "false"; + } + return false; +} + +void JsonNode::clear() +{ + setType(JsonType::DATA_NULL); +} + +bool & JsonNode::Bool() +{ + setType(JsonType::DATA_BOOL); + return std::get(data); +} + +double & JsonNode::Float() +{ + setType(JsonType::DATA_FLOAT); + return std::get(data); +} + +si64 & JsonNode::Integer() +{ + setType(JsonType::DATA_INTEGER); + return std::get(data); +} + +std::string & JsonNode::String() +{ + setType(JsonType::DATA_STRING); + return std::get(data); +} + +JsonVector & JsonNode::Vector() +{ + setType(JsonType::DATA_VECTOR); + return std::get(data); +} + +JsonMap & JsonNode::Struct() +{ + setType(JsonType::DATA_STRUCT); + return std::get(data); +} + +const bool boolDefault = false; +bool JsonNode::Bool() const +{ + assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_BOOL); + + if (getType() == JsonType::DATA_BOOL) + return std::get(data); + + return boolDefault; +} + +const double floatDefault = 0; +double JsonNode::Float() const +{ + assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT); + + if(getType() == JsonType::DATA_FLOAT) + return std::get(data); + + if(getType() == JsonType::DATA_INTEGER) + return static_cast(std::get(data)); + + return floatDefault; +} + +const si64 integerDefault = 0; +si64 JsonNode::Integer() const +{ + assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT); + + if(getType() == JsonType::DATA_INTEGER) + return std::get(data); + + if(getType() == JsonType::DATA_FLOAT) + return static_cast(std::get(data)); + + return integerDefault; +} + +const std::string stringDefault = std::string(); +const std::string & JsonNode::String() const +{ + assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRING); + + if (getType() == JsonType::DATA_STRING) + return std::get(data); + + return stringDefault; +} + +const JsonVector vectorDefault = JsonVector(); +const JsonVector & JsonNode::Vector() const +{ + assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_VECTOR); + + if (getType() == JsonType::DATA_VECTOR) + return std::get(data); + + return vectorDefault; +} + +const JsonMap mapDefault = JsonMap(); +const JsonMap & JsonNode::Struct() const +{ + assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRUCT); + + if (getType() == JsonType::DATA_STRUCT) + return std::get(data); + + return mapDefault; +} + +JsonNode & JsonNode::operator[](const std::string & child) +{ + return Struct()[child]; +} + +const JsonNode & JsonNode::operator[](const std::string & child) const +{ + auto it = Struct().find(child); + if (it != Struct().end()) + return it->second; + return nullNode; +} + +JsonNode & JsonNode::operator[](size_t child) +{ + if (child >= Vector().size() ) + Vector().resize(child + 1); + return Vector()[child]; +} + +const JsonNode & JsonNode::operator[](size_t child) const +{ + if (child < Vector().size() ) + return Vector()[child]; + + return nullNode; +} + +const JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) const +{ + return ::resolvePointer(*this, jsonPointer); +} + +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; + JsonWriter writer(out, compact); + writer.writeNode(*this); + return out.str(); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/JsonNode.h b/lib/json/JsonNode.h similarity index 56% rename from lib/JsonNode.h rename to lib/json/JsonNode.h index 75f9128b2..ab4fc37f5 100644 --- a/lib/JsonNode.h +++ b/lib/json/JsonNode.h @@ -8,8 +8,8 @@ * */ #pragma once -#include "GameConstants.h" -#include "filesystem/ResourcePath.h" + +#include "../filesystem/ResourcePath.h" VCMI_LIB_NAMESPACE_BEGIN @@ -127,97 +127,6 @@ public: } }; -namespace JsonUtils -{ - DLL_LINKAGE std::shared_ptr parseBonus(const JsonVector & ability_vec); - DLL_LINKAGE std::shared_ptr parseBonus(const JsonNode & ability); - DLL_LINKAGE std::shared_ptr parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description); - DLL_LINKAGE bool parseBonus(const JsonNode & ability, Bonus * placement); - DLL_LINKAGE std::shared_ptr parseLimiter(const JsonNode & limiter); - DLL_LINKAGE CSelector parseSelector(const JsonNode &ability); - DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node); - - /** - * @brief recursively merges source into dest, replacing identical fields - * struct : recursively calls this function - * arrays : each entry will be merged recursively - * values : value in source will replace value in dest - * null : if value in source is present but set to null it will delete entry in dest - * @note this function will destroy data in source - */ - DLL_LINKAGE void merge(JsonNode & dest, JsonNode & source, bool ignoreOverride = false, bool copyMeta = false); - - /** - * @brief recursively merges source into dest, replacing identical fields - * struct : recursively calls this function - * arrays : each entry will be merged recursively - * values : value in source will replace value in dest - * null : if value in source is present but set to null it will delete entry in dest - * @note this function will preserve data stored in source by creating copy - */ - DLL_LINKAGE void mergeCopy(JsonNode & dest, JsonNode source, bool ignoreOverride = false, bool copyMeta = false); - - /** @brief recursively merges descendant into copy of base node - * Result emulates inheritance semantic - */ - DLL_LINKAGE void inherit(JsonNode & descendant, const JsonNode & base); - - /** - * @brief construct node representing the common structure of input nodes - * @param pruneEmpty - omit common properties whose intersection is empty - * different types: null - * struct: recursive intersect on common properties - * other: input if equal, null otherwise - */ - DLL_LINKAGE JsonNode intersect(const JsonNode & a, const JsonNode & b, bool pruneEmpty = true); - DLL_LINKAGE JsonNode intersect(const std::vector & nodes, bool pruneEmpty = true); - - /** - * @brief construct node representing the difference "node - base" - * merging difference with base gives node - */ - DLL_LINKAGE JsonNode difference(const JsonNode & node, const JsonNode & base); - - /** - * @brief generate one Json structure from multiple files - * @param files - list of filenames with parts of json structure - */ - DLL_LINKAGE JsonNode assembleFromFiles(const std::vector & files); - DLL_LINKAGE JsonNode assembleFromFiles(const std::vector & files, bool & isValid); - - /// This version loads all files with same name (overridden by mods) - DLL_LINKAGE JsonNode assembleFromFiles(const std::string & filename); - - /** - * @brief removes all nodes that are identical to default entry in schema - * @param node - JsonNode to minimize - * @param schemaName - name of schema to use - * @note for minimizing data must be valid against given schema - */ - DLL_LINKAGE void minimize(JsonNode & node, const std::string & schemaName); - /// opposed to minimize, adds all missing, required entries that have default value - DLL_LINKAGE void maximize(JsonNode & node, const std::string & schemaName); - - /** - * @brief validate node against specified schema - * @param node - JsonNode to check - * @param schemaName - name of schema to use - * @param dataName - some way to identify data (printed in console in case of errors) - * @returns true if data in node fully compilant with schema - */ - DLL_LINKAGE bool validate(const JsonNode & node, const std::string & schemaName, const std::string & dataName); - - /// get schema by json URI: vcmi:# - /// example: schema "vcmi:settings" is used to check user settings - DLL_LINKAGE const JsonNode & getSchema(const std::string & URI); - - /// for easy construction of JsonNodes; helps with inserting primitives into vector node - DLL_LINKAGE JsonNode boolNode(bool value); - DLL_LINKAGE JsonNode floatNode(double value); - DLL_LINKAGE JsonNode stringNode(const std::string & value); - DLL_LINKAGE JsonNode intNode(si64 value); -} - namespace JsonDetail { // conversion helpers for JsonNode::convertTo (partial template function instantiation is illegal in c++) diff --git a/lib/json/JsonParser.cpp b/lib/json/JsonParser.cpp new file mode 100644 index 000000000..0d122d53f --- /dev/null +++ b/lib/json/JsonParser.cpp @@ -0,0 +1,465 @@ +/* + * JsonParser.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 "JsonParser.h" + +#include "../TextOperations.h" + +VCMI_LIB_NAMESPACE_BEGIN + +JsonParser::JsonParser(const char * inputString, size_t stringSize): + input(inputString, stringSize), + lineCount(1), + lineStart(0), + pos(0) +{ +} + +JsonNode JsonParser::parse(const std::string & fileName) +{ + JsonNode root; + + if (input.size() == 0) + { + error("File is empty", false); + } + else + { + if (!TextOperations::isValidUnicodeString(&input[0], input.size())) + error("Not a valid UTF-8 file", false); + + extractValue(root); + extractWhitespace(false); + + //Warn if there are any non-whitespace symbols left + if (pos < input.size()) + error("Not all file was parsed!", true); + } + + if (!errors.empty()) + { + logMod->warn("File %s is not a valid JSON file!", fileName); + logMod->warn(errors); + } + return root; +} + +bool JsonParser::isValid() +{ + return errors.empty(); +} + +bool JsonParser::extractSeparator() +{ + if (!extractWhitespace()) + return false; + + if ( input[pos] !=':') + return error("Separator expected"); + + pos++; + return true; +} + +bool JsonParser::extractValue(JsonNode &node) +{ + if (!extractWhitespace()) + return false; + + switch (input[pos]) + { + case '\"': return extractString(node); + case 'n' : return extractNull(node); + case 't' : return extractTrue(node); + case 'f' : return extractFalse(node); + case '{' : return extractStruct(node); + case '[' : return extractArray(node); + case '-' : return extractFloat(node); + default: + { + if (input[pos] >= '0' && input[pos] <= '9') + return extractFloat(node); + return error("Value expected!"); + } + } +} + +bool JsonParser::extractWhitespace(bool verbose) +{ + while (true) + { + while(pos < input.size() && static_cast(input[pos]) <= ' ') + { + if (input[pos] == '\n') + { + lineCount++; + lineStart = pos+1; + } + pos++; + } + if (pos >= input.size() || input[pos] != '/') + break; + + pos++; + if (pos == input.size()) + break; + if (input[pos] == '/') + pos++; + else + error("Comments must consist of two slashes!", true); + + while (pos < input.size() && input[pos] != '\n') + pos++; + } + + if (pos >= input.size() && verbose) + return error("Unexpected end of file!"); + return true; +} + +bool JsonParser::extractEscaping(std::string &str) +{ + switch(input[pos]) + { + break; case '\"': str += '\"'; + break; case '\\': str += '\\'; + break; case 'b': str += '\b'; + break; case 'f': str += '\f'; + break; case 'n': str += '\n'; + break; case 'r': str += '\r'; + break; case 't': str += '\t'; + break; case '/': str += '/'; + break; default: return error("Unknown escape sequence!", true); + } + return true; +} + +bool JsonParser::extractString(std::string &str) +{ + if (input[pos] != '\"') + return error("String expected!"); + pos++; + + size_t first = pos; + + while (pos != input.size()) + { + if (input[pos] == '\"') // Correct end of string + { + str.append( &input[first], pos-first); + pos++; + return true; + } + if (input[pos] == '\\') // Escaping + { + str.append( &input[first], pos-first); + pos++; + if (pos == input.size()) + break; + extractEscaping(str); + first = pos + 1; + } + if (input[pos] == '\n') // end-of-line + { + str.append( &input[first], pos-first); + return error("Closing quote not found!", true); + } + if(static_cast(input[pos]) < ' ') // control character + { + str.append( &input[first], pos-first); + first = pos+1; + error("Illegal character in the string!", true); + } + pos++; + } + return error("Unterminated string!"); +} + +bool JsonParser::extractString(JsonNode &node) +{ + std::string str; + if (!extractString(str)) + return false; + + node.setType(JsonNode::JsonType::DATA_STRING); + node.String() = str; + return true; +} + +bool JsonParser::extractLiteral(const std::string &literal) +{ + if (literal.compare(0, literal.size(), &input[pos], literal.size()) != 0) + { + while (pos < input.size() && ((input[pos]>'a' && input[pos]<'z') + || (input[pos]>'A' && input[pos]<'Z'))) + pos++; + return error("Unknown literal found", true); + } + + pos += literal.size(); + return true; +} + +bool JsonParser::extractNull(JsonNode &node) +{ + if (!extractLiteral("null")) + return false; + + node.clear(); + return true; +} + +bool JsonParser::extractTrue(JsonNode &node) +{ + if (!extractLiteral("true")) + return false; + + node.Bool() = true; + return true; +} + +bool JsonParser::extractFalse(JsonNode &node) +{ + if (!extractLiteral("false")) + return false; + + node.Bool() = false; + return true; +} + +bool JsonParser::extractStruct(JsonNode &node) +{ + node.setType(JsonNode::JsonType::DATA_STRUCT); + pos++; + + if (!extractWhitespace()) + return false; + + //Empty struct found + if (input[pos] == '}') + { + pos++; + return true; + } + + while (true) + { + if (!extractWhitespace()) + return false; + + std::string key; + if (!extractString(key)) + return false; + + // split key string into actual key and meta-flags + std::vector keyAndFlags; + boost::split(keyAndFlags, key, boost::is_any_of("#")); + key = keyAndFlags[0]; + // check for unknown flags - helps with debugging + std::vector knownFlags = { "override" }; + for(int i = 1; i < keyAndFlags.size(); i++) + { + if(!vstd::contains(knownFlags, keyAndFlags[i])) + error("Encountered unknown flag #" + keyAndFlags[i], true); + } + + if (node.Struct().find(key) != node.Struct().end()) + error("Duplicate element encountered!", true); + + if (!extractSeparator()) + return false; + + if (!extractElement(node.Struct()[key], '}')) + return false; + + // flags from key string belong to referenced element + for(int i = 1; i < keyAndFlags.size(); i++) + node.Struct()[key].flags.push_back(keyAndFlags[i]); + + if (input[pos] == '}') + { + pos++; + return true; + } + } +} + +bool JsonParser::extractArray(JsonNode &node) +{ + pos++; + node.setType(JsonNode::JsonType::DATA_VECTOR); + + if (!extractWhitespace()) + return false; + + //Empty array found + if (input[pos] == ']') + { + pos++; + return true; + } + + while (true) + { + //NOTE: currently 50% of time is this vector resizing. + //May be useful to use list during parsing and then swap() all items to vector + node.Vector().resize(node.Vector().size()+1); + + if (!extractElement(node.Vector().back(), ']')) + return false; + + if (input[pos] == ']') + { + pos++; + return true; + } + } +} + +bool JsonParser::extractElement(JsonNode &node, char terminator) +{ + if (!extractValue(node)) + return false; + + if (!extractWhitespace()) + return false; + + bool comma = (input[pos] == ','); + if (comma ) + { + pos++; + if (!extractWhitespace()) + return false; + } + + if (input[pos] == terminator) + { + //FIXME: MOD COMPATIBILITY: Too many of these right now, re-enable later + //if (comma) + //error("Extra comma found!", true); + return true; + } + + if (!comma) + error("Comma expected!", true); + + return true; +} + +bool JsonParser::extractFloat(JsonNode &node) +{ + assert(input[pos] == '-' || (input[pos] >= '0' && input[pos] <= '9')); + bool negative=false; + double result=0; + si64 integerPart = 0; + bool isFloat = false; + + if (input[pos] == '-') + { + pos++; + negative = true; + } + + if (input[pos] < '0' || input[pos] > '9') + return error("Number expected!"); + + //Extract integer part + while (input[pos] >= '0' && input[pos] <= '9') + { + integerPart = integerPart*10+(input[pos]-'0'); + pos++; + } + + result = static_cast(integerPart); + + if (input[pos] == '.') + { + //extract fractional part + isFloat = true; + pos++; + double fractMult = 0.1; + if (input[pos] < '0' || input[pos] > '9') + return error("Decimal part expected!"); + + while (input[pos] >= '0' && input[pos] <= '9') + { + result = result + fractMult*(input[pos]-'0'); + fractMult /= 10; + pos++; + } + } + + if(input[pos] == 'e') + { + //extract exponential part + pos++; + isFloat = true; + bool powerNegative = false; + double power = 0; + + if(input[pos] == '-') + { + pos++; + powerNegative = true; + } + else if(input[pos] == '+') + { + pos++; + } + + if (input[pos] < '0' || input[pos] > '9') + return error("Exponential part expected!"); + + while (input[pos] >= '0' && input[pos] <= '9') + { + power = power*10 + (input[pos]-'0'); + pos++; + } + + if(powerNegative) + power = -power; + + result *= std::pow(10, power); + } + + if(isFloat) + { + if(negative) + result = -result; + + node.setType(JsonNode::JsonType::DATA_FLOAT); + node.Float() = result; + } + else + { + if(negative) + integerPart = -integerPart; + + node.setType(JsonNode::JsonType::DATA_INTEGER); + node.Integer() = integerPart; + } + + return true; +} + +bool JsonParser::error(const std::string &message, bool warning) +{ + std::ostringstream stream; + std::string type(warning?" warning: ":" error: "); + + stream << "At line " << lineCount << ", position "< JsonNode conversion +class JsonParser +{ + std::string errors; // Contains description of all encountered errors + constString input; // Input data + ui32 lineCount; // Currently parsed line, starting from 1 + size_t lineStart; // Position of current line start + size_t pos; // Current position of parser + + //Helpers + bool extractEscaping(std::string &str); + bool extractLiteral(const std::string &literal); + bool extractString(std::string &string); + bool extractWhitespace(bool verbose = true); + bool extractSeparator(); + bool extractElement(JsonNode &node, char terminator); + + //Methods for extracting JSON data + bool extractArray(JsonNode &node); + bool extractFalse(JsonNode &node); + bool extractFloat(JsonNode &node); + bool extractNull(JsonNode &node); + bool extractString(JsonNode &node); + bool extractStruct(JsonNode &node); + bool extractTrue(JsonNode &node); + bool extractValue(JsonNode &node); + + //Add error\warning message to list + bool error(const std::string &message, bool warning=false); + +public: + JsonParser(const char * inputString, size_t stringSize); + + /// do actual parsing. filename is name of file that will printed to console if any errors were found + JsonNode parse(const std::string & fileName); + + /// returns true if parsing was successful + bool isValid(); +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/JsonRandom.cpp b/lib/json/JsonRandom.cpp similarity index 97% rename from lib/JsonRandom.cpp rename to lib/json/JsonRandom.cpp index 9809a7f00..e9527f4a2 100644 --- a/lib/JsonRandom.cpp +++ b/lib/json/JsonRandom.cpp @@ -13,21 +13,22 @@ #include -#include "JsonNode.h" -#include "CRandomGenerator.h" -#include "constants/StringConstants.h" -#include "VCMI_Lib.h" -#include "CArtHandler.h" -#include "CCreatureHandler.h" -#include "CCreatureSet.h" -#include "spells/CSpellHandler.h" -#include "CSkillHandler.h" -#include "CHeroHandler.h" -#include "IGameCallback.h" -#include "gameState/CGameState.h" -#include "mapObjects/IObjectInterface.h" -#include "modding/IdentifierStorage.h" -#include "modding/ModScope.h" +#include "JsonUtils.h" + +#include "../CRandomGenerator.h" +#include "../constants/StringConstants.h" +#include "../VCMI_Lib.h" +#include "../CArtHandler.h" +#include "../CCreatureHandler.h" +#include "../CCreatureSet.h" +#include "../spells/CSpellHandler.h" +#include "../CSkillHandler.h" +#include "../CHeroHandler.h" +#include "../IGameCallback.h" +#include "../gameState/CGameState.h" +#include "../mapObjects/IObjectInterface.h" +#include "../modding/IdentifierStorage.h" +#include "../modding/ModScope.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/JsonRandom.h b/lib/json/JsonRandom.h similarity index 100% rename from lib/JsonRandom.h rename to lib/json/JsonRandom.h diff --git a/lib/JsonNode.cpp b/lib/json/JsonUtils.cpp similarity index 77% rename from lib/JsonNode.cpp rename to lib/json/JsonUtils.cpp index f0a6334f8..51f8c9900 100644 --- a/lib/JsonNode.cpp +++ b/lib/json/JsonUtils.cpp @@ -1,5 +1,5 @@ /* - * JsonNode.cpp, part of VCMI engine + * JsonUtils.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -9,437 +9,25 @@ */ #include "StdInc.h" -#include "JsonNode.h" +#include "JsonUtils.h" -#include "ScopeGuard.h" +#include "JsonValidator.h" -#include "bonuses/BonusParams.h" -#include "bonuses/Bonus.h" -#include "bonuses/Limiters.h" -#include "bonuses/Propagators.h" -#include "bonuses/Updaters.h" -#include "filesystem/Filesystem.h" -#include "modding/IdentifierStorage.h" -#include "VCMI_Lib.h" //for identifier resolution -#include "CGeneralTextHandler.h" -#include "JsonDetail.h" -#include "constants/StringConstants.h" -#include "battle/BattleHex.h" - -namespace -{ -// to avoid duplicating const and non-const code -template -Node & resolvePointer(Node & in, const std::string & pointer) -{ - if(pointer.empty()) - return in; - assert(pointer[0] == '/'); - - size_t splitPos = pointer.find('/', 1); - - std::string entry = pointer.substr(1, splitPos - 1); - std::string remainer = splitPos == std::string::npos ? "" : pointer.substr(splitPos); - - if(in.getType() == VCMI_LIB_WRAP_NAMESPACE(JsonNode)::JsonType::DATA_VECTOR) - { - if(entry.find_first_not_of("0123456789") != std::string::npos) // non-numbers in string - throw std::runtime_error("Invalid Json pointer"); - - if(entry.size() > 1 && entry[0] == '0') // leading zeros are not allowed - throw std::runtime_error("Invalid Json pointer"); - - auto index = boost::lexical_cast(entry); - - if (in.Vector().size() > index) - return in.Vector()[index].resolvePointer(remainer); - } - return in[entry].resolvePointer(remainer); -} -} - -VCMI_LIB_NAMESPACE_BEGIN - -using namespace JsonDetail; - -class LibClasses; -class CModHandler; +#include "../ScopeGuard.h" +#include "../bonuses/BonusParams.h" +#include "../bonuses/Bonus.h" +#include "../bonuses/Limiters.h" +#include "../bonuses/Propagators.h" +#include "../bonuses/Updaters.h" +#include "../filesystem/Filesystem.h" +#include "../modding/IdentifierStorage.h" +#include "../VCMI_Lib.h" //for identifier resolution +#include "../CGeneralTextHandler.h" +#include "../constants/StringConstants.h" +#include "../battle/BattleHex.h" static const JsonNode nullNode; -JsonNode::JsonNode(JsonType Type) -{ - setType(Type); -} - -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); - *this = parser.parse(""); -} - -JsonNode::JsonNode(const JsonPath & fileURI) -{ - auto file = CResourceHandler::get()->load(fileURI)->readAll(); - - JsonParser parser(reinterpret_cast(file.first.get()), file.second); - *this = parser.parse(fileURI.getName()); -} - -JsonNode::JsonNode(const std::string & idx, const JsonPath & fileURI) -{ - auto file = CResourceHandler::get(idx)->load(fileURI)->readAll(); - - JsonParser parser(reinterpret_cast(file.first.get()), file.second); - *this = parser.parse(fileURI.getName()); -} - -JsonNode::JsonNode(const JsonPath & fileURI, bool &isValidSyntax) -{ - auto file = CResourceHandler::get()->load(fileURI)->readAll(); - - JsonParser parser(reinterpret_cast(file.first.get()), file.second); - *this = parser.parse(fileURI.getName()); - isValidSyntax = parser.isValid(); -} - -bool JsonNode::operator == (const JsonNode &other) const -{ - return data == other.data; -} - -bool JsonNode::operator != (const JsonNode &other) const -{ - return !(*this == other); -} - -JsonNode::JsonType JsonNode::getType() const -{ - return static_cast(data.index()); -} - -void JsonNode::setMeta(const std::string & metadata, bool recursive) -{ - meta = metadata; - if (recursive) - { - switch (getType()) - { - break; case JsonType::DATA_VECTOR: - { - for(auto & node : Vector()) - { - node.setMeta(metadata); - } - } - break; case JsonType::DATA_STRUCT: - { - for(auto & node : Struct()) - { - node.second.setMeta(metadata); - } - } - } - } -} - -void JsonNode::setType(JsonType Type) -{ - if (getType() == Type) - return; - - //float<->int conversion - if(getType() == JsonType::DATA_FLOAT && Type == JsonType::DATA_INTEGER) - { - si64 converted = static_cast(std::get(data)); - data = JsonData(converted); - return; - } - else if(getType() == JsonType::DATA_INTEGER && Type == JsonType::DATA_FLOAT) - { - double converted = static_cast(std::get(data)); - data = JsonData(converted); - return; - } - - //Set new node type - switch(Type) - { - break; case JsonType::DATA_NULL: data = JsonData(); - break; case JsonType::DATA_BOOL: data = JsonData(false); - break; case JsonType::DATA_FLOAT: data = JsonData(static_cast(0.0)); - break; case JsonType::DATA_STRING: data = JsonData(std::string()); - break; case JsonType::DATA_VECTOR: data = JsonData(JsonVector()); - break; case JsonType::DATA_STRUCT: data = JsonData(JsonMap()); - break; case JsonType::DATA_INTEGER: data = JsonData(static_cast(0)); - } -} - -bool JsonNode::isNull() const -{ - return getType() == JsonType::DATA_NULL; -} - -bool JsonNode::isNumber() const -{ - return getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT; -} - -bool JsonNode::isString() const -{ - return getType() == JsonType::DATA_STRING; -} - -bool JsonNode::isVector() const -{ - return getType() == JsonType::DATA_VECTOR; -} - -bool JsonNode::isStruct() const -{ - return getType() == JsonType::DATA_STRUCT; -} - -bool JsonNode::containsBaseData() const -{ - switch(getType()) - { - case JsonType::DATA_NULL: - return false; - case JsonType::DATA_STRUCT: - for(const auto & elem : Struct()) - { - if(elem.second.containsBaseData()) - return true; - } - return false; - default: - //other types (including vector) cannot be extended via merge - return true; - } -} - -bool JsonNode::isCompact() const -{ - switch(getType()) - { - case JsonType::DATA_VECTOR: - for(const JsonNode & elem : Vector()) - { - if(!elem.isCompact()) - return false; - } - return true; - case JsonType::DATA_STRUCT: - { - auto propertyCount = Struct().size(); - if(propertyCount == 0) - return true; - else if(propertyCount == 1) - return Struct().begin()->second.isCompact(); - } - return false; - default: - return true; - } -} - -bool JsonNode::TryBoolFromString(bool & success) const -{ - success = true; - if(getType() == JsonNode::JsonType::DATA_BOOL) - return Bool(); - - success = getType() == JsonNode::JsonType::DATA_STRING; - if(success) - { - auto boolParamStr = String(); - boost::algorithm::trim(boolParamStr); - boost::algorithm::to_lower(boolParamStr); - success = boolParamStr == "true"; - - if(success) - return true; - - success = boolParamStr == "false"; - } - return false; -} - -void JsonNode::clear() -{ - setType(JsonType::DATA_NULL); -} - -bool & JsonNode::Bool() -{ - setType(JsonType::DATA_BOOL); - return std::get(data); -} - -double & JsonNode::Float() -{ - setType(JsonType::DATA_FLOAT); - return std::get(data); -} - -si64 & JsonNode::Integer() -{ - setType(JsonType::DATA_INTEGER); - return std::get(data); -} - -std::string & JsonNode::String() -{ - setType(JsonType::DATA_STRING); - return std::get(data); -} - -JsonVector & JsonNode::Vector() -{ - setType(JsonType::DATA_VECTOR); - return std::get(data); -} - -JsonMap & JsonNode::Struct() -{ - setType(JsonType::DATA_STRUCT); - return std::get(data); -} - -const bool boolDefault = false; -bool JsonNode::Bool() const -{ - assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_BOOL); - - if (getType() == JsonType::DATA_BOOL) - return std::get(data); - - return boolDefault; -} - -const double floatDefault = 0; -double JsonNode::Float() const -{ - assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT); - - if(getType() == JsonType::DATA_FLOAT) - return std::get(data); - - if(getType() == JsonType::DATA_INTEGER) - return static_cast(std::get(data)); - - return floatDefault; -} - -const si64 integerDefault = 0; -si64 JsonNode::Integer() const -{ - assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT); - - if(getType() == JsonType::DATA_INTEGER) - return std::get(data); - - if(getType() == JsonType::DATA_FLOAT) - return static_cast(std::get(data)); - - return integerDefault; -} - -const std::string stringDefault = std::string(); -const std::string & JsonNode::String() const -{ - assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRING); - - if (getType() == JsonType::DATA_STRING) - return std::get(data); - - return stringDefault; -} - -const JsonVector vectorDefault = JsonVector(); -const JsonVector & JsonNode::Vector() const -{ - assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_VECTOR); - - if (getType() == JsonType::DATA_VECTOR) - return std::get(data); - - return vectorDefault; -} - -const JsonMap mapDefault = JsonMap(); -const JsonMap & JsonNode::Struct() const -{ - assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRUCT); - - if (getType() == JsonType::DATA_STRUCT) - return std::get(data); - - return mapDefault; -} - -JsonNode & JsonNode::operator[](const std::string & child) -{ - return Struct()[child]; -} - -const JsonNode & JsonNode::operator[](const std::string & child) const -{ - auto it = Struct().find(child); - if (it != Struct().end()) - return it->second; - return nullNode; -} - -JsonNode & JsonNode::operator[](size_t child) -{ - if (child >= Vector().size() ) - Vector().resize(child + 1); - return Vector()[child]; -} - -const JsonNode & JsonNode::operator[](size_t child) const -{ - if (child < Vector().size() ) - return Vector()[child]; - - return nullNode; -} - -const JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) const -{ - return ::resolvePointer(*this, jsonPointer); -} - -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; - JsonWriter writer(out, compact); - writer.writeNode(*this); - return out.str(); -} - -///JsonUtils - static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node) { if (node.isNull()) @@ -1145,7 +733,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b) if (!value->isNull()) { //ALL_CREATURES old propagator compatibility - if(value->String() == "ALL_CREATURES") + if(value->String() == "ALL_CREATURES") { logMod->warn("ALL_CREATURES propagator is deprecated. Use GLOBAL_EFFECT propagator with CREATURES_ONLY limiter"); b->addLimiter(std::make_shared()); @@ -1182,7 +770,7 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability) CSelector base = Selector::none; for(const auto & andN : value->Vector()) base = base.Or(parseSelector(andN)); - + ret = ret.And(base); } @@ -1192,7 +780,7 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability) CSelector base = Selector::none; for(const auto & andN : value->Vector()) base = base.Or(parseSelector(andN)); - + ret = ret.And(base.Not()); } @@ -1237,7 +825,7 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability) else if(src) ret = ret.And(Selector::sourceTypeSel(*src)); - + value = &ability["targetSourceType"]; if(value->isString()) { @@ -1649,5 +1237,3 @@ DLL_LINKAGE JsonNode JsonUtils::intNode(si64 value) node.Integer() = value; return node; } - -VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonUtils.h b/lib/json/JsonUtils.h new file mode 100644 index 000000000..2a733655e --- /dev/null +++ b/lib/json/JsonUtils.h @@ -0,0 +1,104 @@ +/* + * JsonUtils.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 "JsonNode.h" +#include "../GameConstants.h" + +namespace JsonUtils +{ + DLL_LINKAGE std::shared_ptr parseBonus(const JsonVector & ability_vec); + DLL_LINKAGE std::shared_ptr parseBonus(const JsonNode & ability); + DLL_LINKAGE std::shared_ptr parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description); + DLL_LINKAGE bool parseBonus(const JsonNode & ability, Bonus * placement); + DLL_LINKAGE std::shared_ptr parseLimiter(const JsonNode & limiter); + DLL_LINKAGE CSelector parseSelector(const JsonNode &ability); + DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node); + + /** + * @brief recursively merges source into dest, replacing identical fields + * struct : recursively calls this function + * arrays : each entry will be merged recursively + * values : value in source will replace value in dest + * null : if value in source is present but set to null it will delete entry in dest + * @note this function will destroy data in source + */ + DLL_LINKAGE void merge(JsonNode & dest, JsonNode & source, bool ignoreOverride = false, bool copyMeta = false); + + /** + * @brief recursively merges source into dest, replacing identical fields + * struct : recursively calls this function + * arrays : each entry will be merged recursively + * values : value in source will replace value in dest + * null : if value in source is present but set to null it will delete entry in dest + * @note this function will preserve data stored in source by creating copy + */ + DLL_LINKAGE void mergeCopy(JsonNode & dest, JsonNode source, bool ignoreOverride = false, bool copyMeta = false); + + /** @brief recursively merges descendant into copy of base node + * Result emulates inheritance semantic + */ + DLL_LINKAGE void inherit(JsonNode & descendant, const JsonNode & base); + + /** + * @brief construct node representing the common structure of input nodes + * @param pruneEmpty - omit common properties whose intersection is empty + * different types: null + * struct: recursive intersect on common properties + * other: input if equal, null otherwise + */ + DLL_LINKAGE JsonNode intersect(const JsonNode & a, const JsonNode & b, bool pruneEmpty = true); + DLL_LINKAGE JsonNode intersect(const std::vector & nodes, bool pruneEmpty = true); + + /** + * @brief construct node representing the difference "node - base" + * merging difference with base gives node + */ + DLL_LINKAGE JsonNode difference(const JsonNode & node, const JsonNode & base); + + /** + * @brief generate one Json structure from multiple files + * @param files - list of filenames with parts of json structure + */ + DLL_LINKAGE JsonNode assembleFromFiles(const std::vector & files); + DLL_LINKAGE JsonNode assembleFromFiles(const std::vector & files, bool & isValid); + + /// This version loads all files with same name (overridden by mods) + DLL_LINKAGE JsonNode assembleFromFiles(const std::string & filename); + + /** + * @brief removes all nodes that are identical to default entry in schema + * @param node - JsonNode to minimize + * @param schemaName - name of schema to use + * @note for minimizing data must be valid against given schema + */ + DLL_LINKAGE void minimize(JsonNode & node, const std::string & schemaName); + /// opposed to minimize, adds all missing, required entries that have default value + DLL_LINKAGE void maximize(JsonNode & node, const std::string & schemaName); + + /** + * @brief validate node against specified schema + * @param node - JsonNode to check + * @param schemaName - name of schema to use + * @param dataName - some way to identify data (printed in console in case of errors) + * @returns true if data in node fully compilant with schema + */ + DLL_LINKAGE bool validate(const JsonNode & node, const std::string & schemaName, const std::string & dataName); + + /// get schema by json URI: vcmi:# + /// example: schema "vcmi:settings" is used to check user settings + DLL_LINKAGE const JsonNode & getSchema(const std::string & URI); + + /// for easy construction of JsonNodes; helps with inserting primitives into vector node + DLL_LINKAGE JsonNode boolNode(bool value); + DLL_LINKAGE JsonNode floatNode(double value); + DLL_LINKAGE JsonNode stringNode(const std::string & value); + DLL_LINKAGE JsonNode intNode(si64 value); +} diff --git a/lib/json/JsonValidator.cpp b/lib/json/JsonValidator.cpp new file mode 100644 index 000000000..c2941081c --- /dev/null +++ b/lib/json/JsonValidator.cpp @@ -0,0 +1,687 @@ +/* + * JsonValidator.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 "JsonValidator.h" + +#include "JsonUtils.h" + +#include "../VCMI_Lib.h" +#include "../filesystem/Filesystem.h" +#include "../modding/ModScope.h" +#include "../modding/CModHandler.h" +#include "../ScopeGuard.h" + +VCMI_LIB_NAMESPACE_BEGIN + +//TODO: integer support + +static const std::unordered_map stringToType = +{ + {"null", JsonNode::JsonType::DATA_NULL}, + {"boolean", JsonNode::JsonType::DATA_BOOL}, + {"number", JsonNode::JsonType::DATA_FLOAT}, + {"string", JsonNode::JsonType::DATA_STRING}, + {"array", JsonNode::JsonType::DATA_VECTOR}, + {"object", JsonNode::JsonType::DATA_STRUCT} +}; + +namespace +{ + namespace Common + { + std::string emptyCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + // check is not needed - e.g. incorporated into another check + return ""; + } + + std::string notImplementedCheck(Validation::ValidationData & validator, + const JsonNode & baseSchema, + const JsonNode & schema, + const JsonNode & data) + { + return "Not implemented entry in schema"; + } + + std::string schemaListCheck(Validation::ValidationData & validator, + const JsonNode & baseSchema, + const JsonNode & schema, + const JsonNode & data, + const std::string & errorMsg, + const std::function & isValid) + { + std::string errors = "\n"; + size_t result = 0; + + for(const auto & schemaEntry : schema.Vector()) + { + std::string error = check(schemaEntry, data, validator); + if (error.empty()) + { + result++; + } + else + { + errors += error; + errors += "\n"; + } + } + if (isValid(result)) + return ""; + else + return validator.makeErrorMessage(errorMsg) + errors; + } + + std::string allOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass all schemas", [&](size_t count) + { + return count == schema.Vector().size(); + }); + } + + std::string anyOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass any schema", [&](size_t count) + { + return count > 0; + }); + } + + std::string oneOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass exactly one schema", [&](size_t count) + { + return count == 1; + }); + } + + std::string notCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + if (check(schema, data, validator).empty()) + return validator.makeErrorMessage("Successful validation against negative check"); + return ""; + } + + std::string enumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + for(const auto & enumEntry : schema.Vector()) + { + if (data == enumEntry) + return ""; + } + return validator.makeErrorMessage("Key must have one of predefined values"); + } + + std::string typeCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + const auto & typeName = schema.String(); + auto it = stringToType.find(typeName); + if(it == stringToType.end()) + { + return validator.makeErrorMessage("Unknown type in schema:" + typeName); + } + + JsonNode::JsonType type = it->second; + + //FIXME: hack for integer values + if(data.isNumber() && type == JsonNode::JsonType::DATA_FLOAT) + return ""; + + if(type != data.getType() && data.getType() != JsonNode::JsonType::DATA_NULL) + return validator.makeErrorMessage("Type mismatch! Expected " + schema.String()); + return ""; + } + + std::string refCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + std::string URI = schema.String(); + //node must be validated using schema pointed by this reference and not by data here + //Local reference. Turn it into more easy to handle remote ref + if (boost::algorithm::starts_with(URI, "#")) + { + const std::string name = validator.usedSchemas.back(); + const std::string nameClean = name.substr(0, name.find('#')); + URI = nameClean + URI; + } + return check(URI, data, validator); + } + + std::string formatCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + auto formats = Validation::getKnownFormats(); + std::string errors; + auto checker = formats.find(schema.String()); + if (checker != formats.end()) + { + if (data.isString()) + { + std::string result = checker->second(data); + if (!result.empty()) + errors += validator.makeErrorMessage(result); + } + else + { + errors += validator.makeErrorMessage("Format value must be string: " + schema.String()); + } + } + else + errors += validator.makeErrorMessage("Unsupported format type: " + schema.String()); + return errors; + } + } + + namespace String + { + std::string maxLengthCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + if (data.String().size() > schema.Float()) + return validator.makeErrorMessage((boost::format("String is longer than %d symbols") % schema.Float()).str()); + return ""; + } + + std::string minLengthCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + if (data.String().size() < schema.Float()) + return validator.makeErrorMessage((boost::format("String is shorter than %d symbols") % schema.Float()).str()); + return ""; + } + } + + namespace Number + { + + std::string maximumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + if (baseSchema["exclusiveMaximum"].Bool()) + { + if (data.Float() >= schema.Float()) + return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str()); + } + else + { + if (data.Float() > schema.Float()) + return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str()); + } + return ""; + } + + std::string minimumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + if (baseSchema["exclusiveMinimum"].Bool()) + { + if (data.Float() <= schema.Float()) + return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str()); + } + else + { + if (data.Float() < schema.Float()) + return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str()); + } + return ""; + } + + std::string multipleOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + double result = data.Float() / schema.Float(); + if (floor(result) != result) + return validator.makeErrorMessage((boost::format("Value is not divisible by %d") % schema.Float()).str()); + return ""; + } + } + + namespace Vector + { + std::string itemEntryCheck(Validation::ValidationData & validator, const JsonVector & items, const JsonNode & schema, size_t index) + { + validator.currentPath.emplace_back(); + validator.currentPath.back().Float() = static_cast(index); + auto onExit = vstd::makeScopeGuard([&]() + { + validator.currentPath.pop_back(); + }); + + if (!schema.isNull()) + return check(schema, items[index], validator); + return ""; + } + + std::string itemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + std::string errors; + for (size_t i=0; i i) + errors += itemEntryCheck(validator, data.Vector(), schema.Vector()[i], i); + } + else + { + errors += itemEntryCheck(validator, data.Vector(), schema, i); + } + } + return errors; + } + + std::string additionalItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + std::string errors; + // "items" is struct or empty (defaults to empty struct) - validation always successful + const JsonNode & items = baseSchema["items"]; + if (items.getType() != JsonNode::JsonType::DATA_VECTOR) + return ""; + + for (size_t i=items.Vector().size(); i schema.Float()) + return validator.makeErrorMessage((boost::format("Length is bigger than %d") % schema.Float()).str()); + return ""; + } + + std::string uniqueItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + if (schema.Bool()) + { + for (auto itA = schema.Vector().begin(); itA != schema.Vector().end(); itA++) + { + auto itB = itA; + while (++itB != schema.Vector().end()) + { + if (*itA == *itB) + return validator.makeErrorMessage("List must consist from unique items"); + } + } + } + return ""; + } + } + + namespace Struct + { + std::string maxPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + if (data.Struct().size() > schema.Float()) + return validator.makeErrorMessage((boost::format("Number of entries is bigger than %d") % schema.Float()).str()); + return ""; + } + + std::string minPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + if (data.Struct().size() < schema.Float()) + return validator.makeErrorMessage((boost::format("Number of entries is less than %d") % schema.Float()).str()); + return ""; + } + + std::string uniquePropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + for (auto itA = data.Struct().begin(); itA != data.Struct().end(); itA++) + { + auto itB = itA; + while (++itB != data.Struct().end()) + { + if (itA->second == itB->second) + return validator.makeErrorMessage("List must consist from unique items"); + } + } + return ""; + } + + std::string requiredCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + std::string errors; + for(const auto & required : schema.Vector()) + { + if (data[required.String()].isNull()) + errors += validator.makeErrorMessage("Required entry " + required.String() + " is missing"); + } + return errors; + } + + std::string dependenciesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + std::string errors; + for(const auto & deps : schema.Struct()) + { + if (!data[deps.first].isNull()) + { + if (deps.second.getType() == JsonNode::JsonType::DATA_VECTOR) + { + JsonVector depList = deps.second.Vector(); + for(auto & depEntry : depList) + { + if (data[depEntry.String()].isNull()) + errors += validator.makeErrorMessage("Property " + depEntry.String() + " required for " + deps.first + " is missing"); + } + } + else + { + if (!check(deps.second, data, validator).empty()) + errors += validator.makeErrorMessage("Requirements for " + deps.first + " are not fulfilled"); + } + } + } + return errors; + } + + std::string propertyEntryCheck(Validation::ValidationData & validator, const JsonNode &node, const JsonNode & schema, const std::string & nodeName) + { + validator.currentPath.emplace_back(); + validator.currentPath.back().String() = nodeName; + auto onExit = vstd::makeScopeGuard([&]() + { + validator.currentPath.pop_back(); + }); + + // there is schema specifically for this item + if (!schema.isNull()) + return check(schema, node, validator); + return ""; + } + + std::string propertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + std::string errors; + + for(const auto & entry : data.Struct()) + errors += propertyEntryCheck(validator, entry.second, schema[entry.first], entry.first); + return errors; + } + + std::string additionalPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + std::string errors; + for(const auto & entry : data.Struct()) + { + if (baseSchema["properties"].Struct().count(entry.first) == 0) + { + // try generic additionalItems schema + if (schema.getType() == JsonNode::JsonType::DATA_STRUCT) + errors += propertyEntryCheck(validator, entry.second, schema, entry.first); + + // or, additionalItems field can be bool which indicates if such items are allowed + else if(!schema.isNull() && !schema.Bool()) // present and set to false - error + errors += validator.makeErrorMessage("Unknown entry found: " + entry.first); + } + } + return errors; + } + } + + namespace Formats + { + bool testFilePresence(const std::string & scope, const ResourcePath & resource) + { + std::set allowedScopes; + if(scope != ModScope::scopeBuiltin() && !scope.empty()) // all real mods may have dependencies + { + //NOTE: recursive dependencies are not allowed at the moment - update code if this changes + bool found = true; + allowedScopes = VLC->modh->getModDependencies(scope, found); + + if(!found) + return false; + + allowedScopes.insert(ModScope::scopeBuiltin()); // all mods can use H3 files + } + allowedScopes.insert(scope); // mods can use their own files + + for(const auto & entry : allowedScopes) + { + if (CResourceHandler::get(entry)->existsResource(resource)) + return true; + } + return false; + } + + #define TEST_FILE(scope, prefix, file, type) \ + if (testFilePresence(scope, ResourcePath(prefix + file, type))) \ + return "" + + std::string testAnimation(const std::string & path, const std::string & scope) + { + TEST_FILE(scope, "Sprites/", path, EResType::ANIMATION); + TEST_FILE(scope, "Sprites/", path, EResType::JSON); + return "Animation file \"" + path + "\" was not found"; + } + + std::string textFile(const JsonNode & node) + { + TEST_FILE(node.meta, "", node.String(), EResType::JSON); + return "Text file \"" + node.String() + "\" was not found"; + } + + std::string musicFile(const JsonNode & node) + { + TEST_FILE(node.meta, "Music/", node.String(), EResType::SOUND); + TEST_FILE(node.meta, "", node.String(), EResType::SOUND); + return "Music file \"" + node.String() + "\" was not found"; + } + + std::string soundFile(const JsonNode & node) + { + TEST_FILE(node.meta, "Sounds/", node.String(), EResType::SOUND); + return "Sound file \"" + node.String() + "\" was not found"; + } + + std::string defFile(const JsonNode & node) + { + return testAnimation(node.String(), node.meta); + } + + std::string animationFile(const JsonNode & node) + { + return testAnimation(node.String(), node.meta); + } + + std::string imageFile(const JsonNode & node) + { + TEST_FILE(node.meta, "Data/", node.String(), EResType::IMAGE); + TEST_FILE(node.meta, "Sprites/", node.String(), EResType::IMAGE); + if (node.String().find(':') != std::string::npos) + return testAnimation(node.String().substr(0, node.String().find(':')), node.meta); + return "Image file \"" + node.String() + "\" was not found"; + } + + std::string videoFile(const JsonNode & node) + { + TEST_FILE(node.meta, "Video/", node.String(), EResType::VIDEO); + return "Video file \"" + node.String() + "\" was not found"; + } + + #undef TEST_FILE + } + + Validation::TValidatorMap createCommonFields() + { + Validation::TValidatorMap ret; + + ret["format"] = Common::formatCheck; + ret["allOf"] = Common::allOfCheck; + ret["anyOf"] = Common::anyOfCheck; + ret["oneOf"] = Common::oneOfCheck; + ret["enum"] = Common::enumCheck; + ret["type"] = Common::typeCheck; + ret["not"] = Common::notCheck; + ret["$ref"] = Common::refCheck; + + // fields that don't need implementation + ret["title"] = Common::emptyCheck; + ret["$schema"] = Common::emptyCheck; + ret["default"] = Common::emptyCheck; + ret["description"] = Common::emptyCheck; + ret["definitions"] = Common::emptyCheck; + return ret; + } + + Validation::TValidatorMap createStringFields() + { + Validation::TValidatorMap ret = createCommonFields(); + ret["maxLength"] = String::maxLengthCheck; + ret["minLength"] = String::minLengthCheck; + + ret["pattern"] = Common::notImplementedCheck; + return ret; + } + + Validation::TValidatorMap createNumberFields() + { + Validation::TValidatorMap ret = createCommonFields(); + ret["maximum"] = Number::maximumCheck; + ret["minimum"] = Number::minimumCheck; + ret["multipleOf"] = Number::multipleOfCheck; + + ret["exclusiveMaximum"] = Common::emptyCheck; + ret["exclusiveMinimum"] = Common::emptyCheck; + return ret; + } + + Validation::TValidatorMap createVectorFields() + { + Validation::TValidatorMap ret = createCommonFields(); + ret["items"] = Vector::itemsCheck; + ret["minItems"] = Vector::minItemsCheck; + ret["maxItems"] = Vector::maxItemsCheck; + ret["uniqueItems"] = Vector::uniqueItemsCheck; + ret["additionalItems"] = Vector::additionalItemsCheck; + return ret; + } + + Validation::TValidatorMap createStructFields() + { + Validation::TValidatorMap ret = createCommonFields(); + ret["additionalProperties"] = Struct::additionalPropertiesCheck; + ret["uniqueProperties"] = Struct::uniquePropertiesCheck; + ret["maxProperties"] = Struct::maxPropertiesCheck; + ret["minProperties"] = Struct::minPropertiesCheck; + ret["dependencies"] = Struct::dependenciesCheck; + ret["properties"] = Struct::propertiesCheck; + ret["required"] = Struct::requiredCheck; + + ret["patternProperties"] = Common::notImplementedCheck; + return ret; + } + + Validation::TFormatMap createFormatMap() + { + Validation::TFormatMap ret; + ret["textFile"] = Formats::textFile; + ret["musicFile"] = Formats::musicFile; + ret["soundFile"] = Formats::soundFile; + ret["defFile"] = Formats::defFile; + ret["animationFile"] = Formats::animationFile; + ret["imageFile"] = Formats::imageFile; + ret["videoFile"] = Formats::videoFile; + + return ret; + } +} + +namespace Validation +{ + std::string ValidationData::makeErrorMessage(const std::string &message) + { + std::string errors; + errors += "At "; + if (!currentPath.empty()) + { + for(const JsonNode &path : currentPath) + { + errors += "/"; + if (path.getType() == JsonNode::JsonType::DATA_STRING) + errors += path.String(); + else + errors += std::to_string(static_cast(path.Float())); + } + } + else + errors += ""; + errors += "\n\t Error: " + message + "\n"; + return errors; + } + + std::string check(const std::string & schemaName, const JsonNode & data) + { + ValidationData validator; + return check(schemaName, data, validator); + } + + std::string check(const std::string & schemaName, const JsonNode & data, ValidationData & validator) + { + validator.usedSchemas.push_back(schemaName); + auto onscopeExit = vstd::makeScopeGuard([&]() + { + validator.usedSchemas.pop_back(); + }); + return check(JsonUtils::getSchema(schemaName), data, validator); + } + + std::string check(const JsonNode & schema, const JsonNode & data, ValidationData & validator) + { + const TValidatorMap & knownFields = getKnownFieldsFor(data.getType()); + std::string errors; + for(const auto & entry : schema.Struct()) + { + auto checker = knownFields.find(entry.first); + if (checker != knownFields.end()) + errors += checker->second(validator, schema, entry.second, data); + //else + // errors += validator.makeErrorMessage("Unknown entry in schema " + entry.first); + } + return errors; + } + + const TValidatorMap & getKnownFieldsFor(JsonNode::JsonType type) + { + static const TValidatorMap commonFields = createCommonFields(); + static const TValidatorMap numberFields = createNumberFields(); + static const TValidatorMap stringFields = createStringFields(); + static const TValidatorMap vectorFields = createVectorFields(); + static const TValidatorMap structFields = createStructFields(); + + switch (type) + { + case JsonNode::JsonType::DATA_FLOAT: + case JsonNode::JsonType::DATA_INTEGER: + return numberFields; + case JsonNode::JsonType::DATA_STRING: return stringFields; + case JsonNode::JsonType::DATA_VECTOR: return vectorFields; + case JsonNode::JsonType::DATA_STRUCT: return structFields; + default: return commonFields; + } + } + + const TFormatMap & getKnownFormats() + { + static TFormatMap knownFormats = createFormatMap(); + return knownFormats; + } + +} // Validation namespace + +VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonValidator.h b/lib/json/JsonValidator.h new file mode 100644 index 000000000..35cc23797 --- /dev/null +++ b/lib/json/JsonValidator.h @@ -0,0 +1,49 @@ +/* + * JsonValidator.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 "JsonNode.h" + +VCMI_LIB_NAMESPACE_BEGIN + + +//Internal class for Json validation. Mostly compilant with json-schema v4 draft +namespace Validation +{ + /// struct used to pass data around during validation + struct ValidationData + { + /// path from root node to current one. + /// JsonNode is used as variant - either string (name of node) or as float (index in list) + std::vector currentPath; + + /// Stack of used schemas. Last schema is the one used currently. + /// May contain multiple items in case if remote references were found + std::vector usedSchemas; + + /// generates error message + std::string makeErrorMessage(const std::string &message); + }; + + using TFormatValidator = std::function; + using TFormatMap = std::unordered_map; + using TFieldValidator = std::function; + using TValidatorMap = std::unordered_map; + + /// map of known fields in schema + const TValidatorMap & getKnownFieldsFor(JsonNode::JsonType type); + const TFormatMap & getKnownFormats(); + + std::string check(const std::string & schemaName, const JsonNode & data); + std::string check(const std::string & schemaName, const JsonNode & data, ValidationData & validator); + std::string check(const JsonNode & schema, const JsonNode & data, ValidationData & validator); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonWriter.cpp b/lib/json/JsonWriter.cpp new file mode 100644 index 000000000..205343d79 --- /dev/null +++ b/lib/json/JsonWriter.cpp @@ -0,0 +1,144 @@ +/* + * JsonWriter.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 "JsonWriter.h" + +VCMI_LIB_NAMESPACE_BEGIN + +template +void JsonWriter::writeContainer(Iterator begin, Iterator end) +{ + if (begin == end) + return; + + prefix += '\t'; + + writeEntry(begin++); + + while (begin != end) + { + out << (compactMode ? ", " : ",\n"); + writeEntry(begin++); + } + + out << (compactMode ? "" : "\n"); + prefix.resize(prefix.size()-1); +} + +void JsonWriter::writeEntry(JsonMap::const_iterator entry) +{ + if(!compactMode) + { + if (!entry->second.meta.empty()) + out << prefix << " // " << entry->second.meta << "\n"; + if(!entry->second.flags.empty()) + out << prefix << " // flags: " << boost::algorithm::join(entry->second.flags, ", ") << "\n"; + out << prefix; + } + writeString(entry->first); + out << " : "; + writeNode(entry->second); +} + +void JsonWriter::writeEntry(JsonVector::const_iterator entry) +{ + if(!compactMode) + { + if (!entry->meta.empty()) + out << prefix << " // " << entry->meta << "\n"; + if(!entry->flags.empty()) + out << prefix << " // flags: " << boost::algorithm::join(entry->flags, ", ") << "\n"; + out << prefix; + } + writeNode(*entry); +} + +void JsonWriter::writeString(const std::string &string) +{ + static const std::string escaped = "\"\\\b\f\n\r\t/"; + + static const std::array escaped_code = {'\"', '\\', 'b', 'f', 'n', 'r', 't', '/'}; + + out <<'\"'; + size_t pos = 0; + size_t start = 0; + for (; pos + void writeContainer(Iterator begin, Iterator end); + void writeEntry(JsonMap::const_iterator entry); + void writeEntry(JsonVector::const_iterator entry); + void writeString(const std::string & string); + void writeNode(const JsonNode & node); + JsonWriter(std::ostream & output, bool compact); +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjectConstructors/AObjectTypeHandler.cpp b/lib/mapObjectConstructors/AObjectTypeHandler.cpp index a3ed75ef4..c3573b761 100644 --- a/lib/mapObjectConstructors/AObjectTypeHandler.cpp +++ b/lib/mapObjectConstructors/AObjectTypeHandler.cpp @@ -13,8 +13,9 @@ #include "IObjectInfo.h" #include "../CGeneralTextHandler.h" -#include "../modding/IdentifierStorage.h" #include "../VCMI_Lib.h" +#include "../json/JsonUtils.h" +#include "../modding/IdentifierStorage.h" #include "../mapObjects/CGObjectInstance.h" #include "../mapObjects/ObjectTemplate.h" diff --git a/lib/mapObjectConstructors/AObjectTypeHandler.h b/lib/mapObjectConstructors/AObjectTypeHandler.h index 4222ff29d..ba298d13f 100644 --- a/lib/mapObjectConstructors/AObjectTypeHandler.h +++ b/lib/mapObjectConstructors/AObjectTypeHandler.h @@ -9,9 +9,9 @@ */ #pragma once +#include "../constants/EntityIdentifiers.h" #include "RandomMapInfo.h" #include "SObjectSounds.h" -#include "../JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapObjectConstructors/CBankInstanceConstructor.cpp b/lib/mapObjectConstructors/CBankInstanceConstructor.cpp index 5b7bf8b9a..170aa1239 100644 --- a/lib/mapObjectConstructors/CBankInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/CBankInstanceConstructor.cpp @@ -10,7 +10,7 @@ #include "StdInc.h" #include "CBankInstanceConstructor.h" -#include "../JsonRandom.h" +#include "../json/JsonRandom.h" #include "../CGeneralTextHandler.h" #include "../IGameCallback.h" #include "../CRandomGenerator.h" diff --git a/lib/mapObjectConstructors/CBankInstanceConstructor.h b/lib/mapObjectConstructors/CBankInstanceConstructor.h index 2bf2251ed..5a609f5d2 100644 --- a/lib/mapObjectConstructors/CBankInstanceConstructor.h +++ b/lib/mapObjectConstructors/CBankInstanceConstructor.h @@ -14,6 +14,7 @@ #include "../CCreatureSet.h" #include "../ResourceSet.h" +#include "../json/JsonNode.h" #include "../mapObjects/CBank.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index b626e7fab..e6ee4f2dd 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -12,12 +12,12 @@ #include "../filesystem/Filesystem.h" #include "../filesystem/CBinaryReader.h" +#include "../json/JsonUtils.h" #include "../VCMI_Lib.h" #include "../GameConstants.h" #include "../constants/StringConstants.h" #include "../CGeneralTextHandler.h" #include "../GameSettings.h" -#include "../JsonNode.h" #include "../CSoundBase.h" #include "../mapObjectConstructors/CBankInstanceConstructor.h" diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.h b/lib/mapObjectConstructors/CObjectClassesHandler.h index cb65b23eb..b1cb1ca64 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.h +++ b/lib/mapObjectConstructors/CObjectClassesHandler.h @@ -9,8 +9,9 @@ */ #pragma once +#include "../constants/EntityIdentifiers.h" #include "../IHandlerBase.h" -#include "../JsonNode.h" +#include "../json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index 9864bd5bf..41ea8f9fc 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -14,7 +14,7 @@ #include "../CHeroHandler.h" #include "../CTownHandler.h" #include "../IGameCallback.h" -#include "../JsonRandom.h" +#include "../json/JsonRandom.h" #include "../constants/StringConstants.h" #include "../TerrainHandler.h" #include "../VCMI_Lib.h" diff --git a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp index ee3d584f5..c52281505 100644 --- a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp @@ -12,7 +12,7 @@ #include "../CCreatureHandler.h" #include "../CGeneralTextHandler.h" -#include "../JsonRandom.h" +#include "../json/JsonRandom.h" #include "../VCMI_Lib.h" #include "../mapObjects/CGDwelling.h" #include "../modding/IdentifierStorage.h" diff --git a/lib/mapObjectConstructors/DwellingInstanceConstructor.h b/lib/mapObjectConstructors/DwellingInstanceConstructor.h index 78d32f60a..bf68fdb26 100644 --- a/lib/mapObjectConstructors/DwellingInstanceConstructor.h +++ b/lib/mapObjectConstructors/DwellingInstanceConstructor.h @@ -10,6 +10,8 @@ #pragma once #include "CDefaultObjectTypeHandler.h" + +#include "../json/JsonNode.h" #include "../mapObjects/CGDwelling.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapObjectConstructors/HillFortInstanceConstructor.h b/lib/mapObjectConstructors/HillFortInstanceConstructor.h index e43ab93c5..645270f17 100644 --- a/lib/mapObjectConstructors/HillFortInstanceConstructor.h +++ b/lib/mapObjectConstructors/HillFortInstanceConstructor.h @@ -10,6 +10,7 @@ #pragma once #include "CDefaultObjectTypeHandler.h" +#include "../json/JsonNode.h" #include "../mapObjects/MiscObjects.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapObjectConstructors/ShipyardInstanceConstructor.h b/lib/mapObjectConstructors/ShipyardInstanceConstructor.h index 334031cd1..8d33b7a76 100644 --- a/lib/mapObjectConstructors/ShipyardInstanceConstructor.h +++ b/lib/mapObjectConstructors/ShipyardInstanceConstructor.h @@ -10,6 +10,7 @@ #pragma once #include "CDefaultObjectTypeHandler.h" +#include "../json/JsonNode.h" #include "../mapObjects/MiscObjects.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index d9ad418a7..0b4dca5b6 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -32,6 +32,7 @@ #include "../StartInfo.h" #include "CGTownInstance.h" #include "../campaign/CampaignState.h" +#include "../json/JsonUtils.h" #include "../pathfinder/TurnInfo.h" #include "../serializer/JsonSerializeFormat.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index bf9c2158d..8226d01ef 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -13,7 +13,7 @@ #include "CGObjectInstance.h" #include "../filesystem/ResourcePath.h" -#include "../JsonNode.h" +#include "../json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 817ca57c7..6fced7254 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -16,7 +16,6 @@ #include "../GameConstants.h" #include "../constants/StringConstants.h" #include "../CGeneralTextHandler.h" -#include "../JsonNode.h" #include "../TerrainHandler.h" #include "../mapObjectConstructors/CRewardableConstructor.h" diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index c67940ac5..74ec81358 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -15,6 +15,7 @@ #include "../VCMI_Lib.h" #include "../CTownHandler.h" #include "../CGeneralTextHandler.h" +#include "../json/JsonUtils.h" #include "../modding/CModHandler.h" #include "../CHeroHandler.h" #include "../Languages.h" diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index 4f72ec311..deaf52e98 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -10,6 +10,8 @@ #pragma once +#include "../constants/EntityIdentifiers.h" +#include "../constants/Enumerations.h" #include "../constants/VariantIdentifier.h" #include "../modding/CModInfo.h" #include "../LogicalExpression.h" diff --git a/lib/mapping/CMapService.cpp b/lib/mapping/CMapService.cpp index 7a7ecbcb3..f78a67ee5 100644 --- a/lib/mapping/CMapService.cpp +++ b/lib/mapping/CMapService.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "CMapService.h" +#include "../json/JsonUtils.h" #include "../filesystem/Filesystem.h" #include "../filesystem/CBinaryReader.h" #include "../filesystem/CCompressedStream.h" diff --git a/lib/mapping/MapEditUtils.cpp b/lib/mapping/MapEditUtils.cpp index 8d9e6a2d7..d0691e082 100644 --- a/lib/mapping/MapEditUtils.cpp +++ b/lib/mapping/MapEditUtils.cpp @@ -12,7 +12,6 @@ #include "MapEditUtils.h" #include "../filesystem/Filesystem.h" -#include "../JsonNode.h" #include "../TerrainHandler.h" #include "CMap.h" #include "CMapOperation.h" diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 90ad01008..9fa0a48dd 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -13,7 +13,7 @@ #include "../filesystem/CInputStream.h" #include "../filesystem/COutputStream.h" -#include "../JsonDetail.h" +#include "../json/JsonWriter.h" #include "CMap.h" #include "MapFormat.h" #include "../ArtifactUtils.h" @@ -1201,7 +1201,7 @@ CMapSaverJson::~CMapSaverJson() = default; void CMapSaverJson::addToArchive(const JsonNode & data, const std::string & filename) { std::ostringstream out; - JsonWriter writer(out); + JsonWriter writer(out, false); writer.writeNode(data); out.flush(); diff --git a/lib/mapping/MapFormatJson.h b/lib/mapping/MapFormatJson.h index fb8a78e2f..117e9f2bb 100644 --- a/lib/mapping/MapFormatJson.h +++ b/lib/mapping/MapFormatJson.h @@ -11,7 +11,6 @@ #pragma once #include "CMapService.h" -#include "../JsonNode.h" #include "../filesystem/CZipSaver.h" #include "../filesystem/CZipLoader.h" diff --git a/lib/mapping/MapIdentifiersH3M.cpp b/lib/mapping/MapIdentifiersH3M.cpp index 0f7207e0c..15a7602ce 100644 --- a/lib/mapping/MapIdentifiersH3M.cpp +++ b/lib/mapping/MapIdentifiersH3M.cpp @@ -11,7 +11,6 @@ #include "StdInc.h" #include "MapIdentifiersH3M.h" -#include "../JsonNode.h" #include "../VCMI_Lib.h" #include "../CTownHandler.h" #include "../CHeroHandler.h" diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 1fe9d4b17..ef08b5009 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -25,6 +25,7 @@ #include "../ScriptHandler.h" #include "../constants/StringConstants.h" #include "../filesystem/Filesystem.h" +#include "../json/JsonUtils.h" #include "../spells/CSpellHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/modding/CModInfo.h b/lib/modding/CModInfo.h index b89fdae02..3d6b40320 100644 --- a/lib/modding/CModInfo.h +++ b/lib/modding/CModInfo.h @@ -9,7 +9,7 @@ */ #pragma once -#include "../JsonNode.h" +#include "../json/JsonNode.h" #include "ModVerificationInfo.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index c255ebc07..0c2abf99a 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -31,6 +31,7 @@ #include "../ScriptHandler.h" #include "../constants/StringConstants.h" #include "../TerrainHandler.h" +#include "../json/JsonUtils.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" #include "../rmg/CRmgTemplateStorage.h" #include "../spells/CSpellHandler.h" diff --git a/lib/modding/ContentTypeHandler.h b/lib/modding/ContentTypeHandler.h index e1224013e..7093c12d5 100644 --- a/lib/modding/ContentTypeHandler.h +++ b/lib/modding/ContentTypeHandler.h @@ -9,7 +9,7 @@ */ #pragma once -#include "../JsonNode.h" +#include "../json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/modding/IdentifierStorage.cpp b/lib/modding/IdentifierStorage.cpp index d8b16d7d0..dd17b15ae 100644 --- a/lib/modding/IdentifierStorage.cpp +++ b/lib/modding/IdentifierStorage.cpp @@ -13,7 +13,6 @@ #include "CModHandler.h" #include "ModScope.h" -#include "../JsonNode.h" #include "../VCMI_Lib.h" #include "../constants/StringConstants.h" #include "../spells/CSpellHandler.h" diff --git a/lib/networkPacks/BattleChanges.h b/lib/networkPacks/BattleChanges.h index 92dca1d47..1a06195bb 100644 --- a/lib/networkPacks/BattleChanges.h +++ b/lib/networkPacks/BattleChanges.h @@ -9,7 +9,7 @@ */ #pragma once -#include "JsonNode.h" +#include "../json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/networkPacks/EntityChanges.h b/lib/networkPacks/EntityChanges.h index 7d5aacf87..f502c309d 100644 --- a/lib/networkPacks/EntityChanges.h +++ b/lib/networkPacks/EntityChanges.h @@ -9,9 +9,9 @@ */ #pragma once -#include +#include "../json/JsonNode.h" -#include "../JsonNode.h" +#include VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/rewardable/Info.cpp b/lib/rewardable/Info.cpp index 334c6a701..8a6fa89cc 100644 --- a/lib/rewardable/Info.cpp +++ b/lib/rewardable/Info.cpp @@ -17,7 +17,7 @@ #include "../CGeneralTextHandler.h" #include "../IGameCallback.h" -#include "../JsonRandom.h" +#include "../json/JsonRandom.h" #include "../mapObjects/IObjectInterface.h" #include "../modding/IdentifierStorage.h" #include "../CRandomGenerator.h" diff --git a/lib/rewardable/Info.h b/lib/rewardable/Info.h index 3216d5f68..e2bb4322f 100644 --- a/lib/rewardable/Info.h +++ b/lib/rewardable/Info.h @@ -10,7 +10,7 @@ #pragma once -#include "../JsonNode.h" +#include "../json/JsonNode.h" #include "../mapObjectConstructors/IObjectInfo.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/serializer/JsonDeserializer.cpp b/lib/serializer/JsonDeserializer.cpp index a2b840777..15d20e989 100644 --- a/lib/serializer/JsonDeserializer.cpp +++ b/lib/serializer/JsonDeserializer.cpp @@ -10,8 +10,6 @@ #include "StdInc.h" #include "JsonDeserializer.h" -#include "../JsonNode.h" - #include VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/serializer/JsonSerializeFormat.cpp b/lib/serializer/JsonSerializeFormat.cpp index bbf576a96..e258d5b83 100644 --- a/lib/serializer/JsonSerializeFormat.cpp +++ b/lib/serializer/JsonSerializeFormat.cpp @@ -10,8 +10,6 @@ #include "StdInc.h" #include "JsonSerializeFormat.h" -#include "../JsonNode.h" - VCMI_LIB_NAMESPACE_BEGIN //JsonSerializeHelper diff --git a/lib/serializer/JsonSerializeFormat.h b/lib/serializer/JsonSerializeFormat.h index b86dbcad4..e5d16021d 100644 --- a/lib/serializer/JsonSerializeFormat.h +++ b/lib/serializer/JsonSerializeFormat.h @@ -9,7 +9,8 @@ */ #pragma once -#include "../JsonNode.h" +#include "../constants/IdentifierBase.h" +#include "../json/JsonNode.h" #include "../modding/IdentifierStorage.h" #include "../modding/ModScope.h" #include "../VCMI_Lib.h" diff --git a/lib/serializer/JsonSerializer.cpp b/lib/serializer/JsonSerializer.cpp index 430a6809a..32575e2e0 100644 --- a/lib/serializer/JsonSerializer.cpp +++ b/lib/serializer/JsonSerializer.cpp @@ -10,8 +10,6 @@ #include "StdInc.h" #include "JsonSerializer.h" -#include "../JsonNode.h" - VCMI_LIB_NAMESPACE_BEGIN JsonSerializer::JsonSerializer(const IInstanceResolver * instanceResolver_, JsonNode & root_): diff --git a/lib/serializer/JsonUpdater.cpp b/lib/serializer/JsonUpdater.cpp index 6a29bc910..d1719a029 100644 --- a/lib/serializer/JsonUpdater.cpp +++ b/lib/serializer/JsonUpdater.cpp @@ -10,10 +10,9 @@ #include "StdInc.h" #include "JsonUpdater.h" -#include "../JsonNode.h" - #include "../bonuses/CBonusSystemNode.h" #include "../bonuses/Bonus.h" +#include "../json/JsonUtils.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index b5751ee53..e5908340a 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -25,7 +25,7 @@ #include "../battle/BattleInfo.h" #include "../battle/CBattleInfoCallback.h" #include "../battle/Unit.h" - +#include "../json/JsonUtils.h" #include "../mapObjects/CGHeroInstance.h" //todo: remove #include "../serializer/CSerializer.h" #include "../modding/IdentifierStorage.h" diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index 522e30d83..b4f079c90 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -13,7 +13,6 @@ #include #include #include -#include "../JsonNode.h" #include "../IHandlerBase.h" #include "../ConstTransitivePtr.h" #include "../int3.h" @@ -21,6 +20,7 @@ #include "../battle/BattleHex.h" #include "../bonuses/Bonus.h" #include "../filesystem/ResourcePath.h" +#include "../json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/spells/TargetCondition.cpp b/lib/spells/TargetCondition.cpp index a3dfeb3de..cabe2aa87 100644 --- a/lib/spells/TargetCondition.cpp +++ b/lib/spells/TargetCondition.cpp @@ -17,7 +17,7 @@ #include "../battle/Unit.h" #include "../bonuses/BonusParams.h" #include "../bonuses/BonusList.h" - +#include "../json/JsonUtils.h" #include "../modding/IdentifierStorage.h" #include "../modding/ModUtility.h" #include "../serializer/JsonSerializeFormat.h" diff --git a/lib/spells/effects/Moat.cpp b/lib/spells/effects/Moat.cpp index 359f2460b..b51e91b1b 100644 --- a/lib/spells/effects/Moat.cpp +++ b/lib/spells/effects/Moat.cpp @@ -18,6 +18,7 @@ #include "../../bonuses/Limiters.h" #include "../../battle/IBattleState.h" #include "../../battle/CBattleInfoCallback.h" +#include "../../json/JsonUtils.h" #include "../../serializer/JsonSerializeFormat.h" #include "../../networkPacks/PacksForClient.h" #include "../../networkPacks/PacksForClientBattle.h" diff --git a/lib/spells/effects/Timed.cpp b/lib/spells/effects/Timed.cpp index 956fcf884..2fa25d090 100644 --- a/lib/spells/effects/Timed.cpp +++ b/lib/spells/effects/Timed.cpp @@ -17,6 +17,7 @@ #include "../../battle/IBattleState.h" #include "../../battle/CBattleInfoCallback.h" #include "../../battle/Unit.h" +#include "../../json/JsonUtils.h" #include "../../mapObjects/CGHeroInstance.h" #include "../../networkPacks/PacksForClientBattle.h" #include "../../networkPacks/SetStackEffect.h" diff --git a/mapeditor/Animation.cpp b/mapeditor/Animation.cpp index 4c12a220c..5aff952da 100644 --- a/mapeditor/Animation.cpp +++ b/mapeditor/Animation.cpp @@ -19,7 +19,7 @@ #include "../lib/vcmi_endian.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/ISimpleResourceLoader.h" -#include "../lib/JsonNode.h" +#include "../lib/json/JsonUtils.h" #include "../lib/CRandomGenerator.h" #include "../lib/VCMIDirs.h" diff --git a/mapeditor/Animation.h b/mapeditor/Animation.h index eb5342f0d..2e4556413 100644 --- a/mapeditor/Animation.h +++ b/mapeditor/Animation.h @@ -10,11 +10,14 @@ #pragma once //code is copied from vcmiclient/CAnimation.h with minimal changes -#include "../lib/JsonNode.h" #include "../lib/GameConstants.h" #include #include +VCMI_LIB_NAMESPACE_BEGIN +class JsonNode; +VCMI_LIB_NAMESPACE_END + /* * Base class for images, can be used for non-animation pictures as well */ diff --git a/mapeditor/graphics.cpp b/mapeditor/graphics.cpp index d80dd6121..73e80b2b3 100644 --- a/mapeditor/graphics.cpp +++ b/mapeditor/graphics.cpp @@ -28,7 +28,6 @@ #include "../CCallback.h" #include "../lib/CGeneralTextHandler.h" #include "BitmapHandler.h" -#include "../lib/JsonNode.h" #include "../lib/CStopWatch.h" #include "../lib/mapObjectConstructors/AObjectTypeHandler.h" #include "../lib/mapObjectConstructors/CObjectClassesHandler.h" diff --git a/mapeditor/jsonutils.cpp b/mapeditor/jsonutils.cpp index a10129c14..15649d97f 100644 --- a/mapeditor/jsonutils.cpp +++ b/mapeditor/jsonutils.cpp @@ -10,6 +10,8 @@ #include "StdInc.h" #include "jsonutils.h" +#include "../lib/json/JsonNode.h" + static QVariantMap JsonToMap(const JsonMap & json) { QVariantMap map; diff --git a/mapeditor/jsonutils.h b/mapeditor/jsonutils.h index 6dd7e7bbf..791711eb0 100644 --- a/mapeditor/jsonutils.h +++ b/mapeditor/jsonutils.h @@ -10,10 +10,11 @@ #pragma once #include -#include "../lib/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN +class JsonNode; + namespace JsonUtils { QVariant toVariant(const JsonNode & node); diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp index 3c11ed278..f5db55ba0 100644 --- a/mapeditor/maphandler.cpp +++ b/mapeditor/maphandler.cpp @@ -22,7 +22,6 @@ #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" #include "../lib/GameConstants.h" -#include "../lib/JsonDetail.h" const int tileSize = 32; diff --git a/mapeditor/resourceExtractor/ResourceConverter.cpp b/mapeditor/resourceExtractor/ResourceConverter.cpp index ed21d3320..54dc3bc22 100644 --- a/mapeditor/resourceExtractor/ResourceConverter.cpp +++ b/mapeditor/resourceExtractor/ResourceConverter.cpp @@ -12,7 +12,6 @@ #include "ResourceConverter.h" -#include "../lib/JsonNode.h" #include "../lib/VCMIDirs.h" #include "../lib/filesystem/Filesystem.h" diff --git a/scripting/lua/LuaScriptingContext.cpp b/scripting/lua/LuaScriptingContext.cpp index 2eda9ac47..d92e43735 100644 --- a/scripting/lua/LuaScriptingContext.cpp +++ b/scripting/lua/LuaScriptingContext.cpp @@ -19,7 +19,7 @@ #include "api/Registry.h" -#include "../../lib/JsonNode.h" +#include "../../lib/json/JsonNode.h" #include "../../lib/filesystem/Filesystem.h" #include "../../lib/battle/IBattleInfoCallback.h" #include "../../lib/CGameInfoCallback.h" diff --git a/scripting/lua/LuaSpellEffect.cpp b/scripting/lua/LuaSpellEffect.cpp index c039567f3..2d51dd281 100644 --- a/scripting/lua/LuaSpellEffect.cpp +++ b/scripting/lua/LuaSpellEffect.cpp @@ -18,6 +18,7 @@ #include "../../lib/battle/Unit.h" #include "../../lib/battle/CBattleInfoCallback.h" +#include "../../lib/json/JsonUtils.h" #include "../../lib/serializer/JsonSerializeFormat.h" static const std::string APPLICABLE_GENERAL = "applicable"; diff --git a/scripting/lua/LuaStack.cpp b/scripting/lua/LuaStack.cpp index 18dc12803..b406a278d 100644 --- a/scripting/lua/LuaStack.cpp +++ b/scripting/lua/LuaStack.cpp @@ -11,7 +11,7 @@ #include "LuaStack.h" -#include "../../lib/JsonNode.h" +#include "../../lib/json/JsonNode.h" #include "../../lib/int3.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/scripting/lua/api/BonusSystem.cpp b/scripting/lua/api/BonusSystem.cpp index 2ce613ab0..f15acebec 100644 --- a/scripting/lua/api/BonusSystem.cpp +++ b/scripting/lua/api/BonusSystem.cpp @@ -11,7 +11,7 @@ #include "BonusSystem.h" -#include "../../../lib/JsonNode.h" +#include "../../../lib/json/JsonNode.h" #include "../../../lib/bonuses/BonusList.h" #include "../../../lib/bonuses/Bonus.h" diff --git a/test/JsonComparer.h b/test/JsonComparer.h index dd10b6950..e98ed039c 100644 --- a/test/JsonComparer.h +++ b/test/JsonComparer.h @@ -10,7 +10,7 @@ #pragma once -#include "../lib/JsonNode.h" +#include "../lib/json/JsonNode.h" namespace vstd { diff --git a/test/entity/CCreatureTest.cpp b/test/entity/CCreatureTest.cpp index a70d3f9c1..3ffdd4d7d 100644 --- a/test/entity/CCreatureTest.cpp +++ b/test/entity/CCreatureTest.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "../../lib/CCreatureHandler.h" +#include "../../lib/json/JsonNode.h" namespace test { diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index 1f2aa6053..29a6a43d5 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -15,6 +15,7 @@ #include "mock/mock_spells_Problem.h" #include "../../lib/VCMIDirs.h" +#include "../../lib/json/JsonUtils.h" #include "../../lib/gameState/CGameState.h" #include "../../lib/networkPacks/PacksForClient.h" #include "../../lib/networkPacks/PacksForClientBattle.h" diff --git a/test/map/CMapEditManagerTest.cpp b/test/map/CMapEditManagerTest.cpp index f25a0434b..0020a6e93 100644 --- a/test/map/CMapEditManagerTest.cpp +++ b/test/map/CMapEditManagerTest.cpp @@ -14,7 +14,6 @@ #include "../lib/mapping/CMapService.h" #include "../lib/mapping/CMap.h" #include "../lib/TerrainHandler.h" -#include "../lib/JsonNode.h" #include "../lib/mapping/CMapEditManager.h" #include "../lib/int3.h" #include "../lib/CRandomGenerator.h" diff --git a/test/map/CMapFormatTest.cpp b/test/map/CMapFormatTest.cpp index cf20dfed1..afd72b8c3 100644 --- a/test/map/CMapFormatTest.cpp +++ b/test/map/CMapFormatTest.cpp @@ -9,8 +9,6 @@ */ #include "StdInc.h" -#include "../../lib/JsonDetail.h" - #include "../../lib/filesystem/CMemoryBuffer.h" #include "../../lib/filesystem/Filesystem.h" diff --git a/test/mock/BattleFake.h b/test/mock/BattleFake.h index 8ce30c38f..4a8d04b4d 100644 --- a/test/mock/BattleFake.h +++ b/test/mock/BattleFake.h @@ -19,7 +19,6 @@ #include "mock_scripting_Pool.h" #endif -#include "../../lib/JsonNode.h" #include "../../lib/battle/CBattleInfoCallback.h" namespace test diff --git a/test/mock/mock_scripting_Context.h b/test/mock/mock_scripting_Context.h index a78cceaa2..365458f57 100644 --- a/test/mock/mock_scripting_Context.h +++ b/test/mock/mock_scripting_Context.h @@ -12,7 +12,7 @@ #include -#include "../../../lib/JsonNode.h" +#include "../../../lib/json/JsonNode.h" namespace scripting { diff --git a/test/scripting/LuaSpellEffectAPITest.cpp b/test/scripting/LuaSpellEffectAPITest.cpp index 759aef93e..78958dfa7 100644 --- a/test/scripting/LuaSpellEffectAPITest.cpp +++ b/test/scripting/LuaSpellEffectAPITest.cpp @@ -12,6 +12,7 @@ #include "ScriptFixture.h" #include "../../lib/networkPacks/PacksForClientBattle.h" +#include "../../lib/json/JsonUtils.h" #include "../mock/mock_ServerCallback.h" diff --git a/test/scripting/LuaSpellEffectTest.cpp b/test/scripting/LuaSpellEffectTest.cpp index bb22c142d..b917c9746 100644 --- a/test/scripting/LuaSpellEffectTest.cpp +++ b/test/scripting/LuaSpellEffectTest.cpp @@ -19,6 +19,7 @@ #include "../mock/mock_ServerCallback.h" #include "../../../lib/VCMI_Lib.h" +#include "../../lib/json/JsonUtils.h" #include "../../../lib/ScriptHandler.h" #include "../../../lib/CScriptingModule.h" diff --git a/test/scripting/ScriptFixture.h b/test/scripting/ScriptFixture.h index ea9f0cbe3..97edbe1fe 100644 --- a/test/scripting/ScriptFixture.h +++ b/test/scripting/ScriptFixture.h @@ -14,7 +14,7 @@ #include -#include "../../lib/JsonNode.h" +#include "../../../lib/json/JsonNode.h" #include "../../lib/ScriptHandler.h" #include "../../lib/battle/CBattleInfoCallback.h" #include "../../lib/bonuses/Bonus.h" diff --git a/test/spells/effects/CloneTest.cpp b/test/spells/effects/CloneTest.cpp index e4523a0d9..810824b6b 100644 --- a/test/spells/effects/CloneTest.cpp +++ b/test/spells/effects/CloneTest.cpp @@ -14,6 +14,7 @@ #include #include "../../../lib/battle/CUnitState.h" +#include "../../../lib/json/JsonNode.h" namespace test { diff --git a/test/spells/effects/DamageTest.cpp b/test/spells/effects/DamageTest.cpp index 05614ade1..a8bae9734 100644 --- a/test/spells/effects/DamageTest.cpp +++ b/test/spells/effects/DamageTest.cpp @@ -14,6 +14,7 @@ #include #include "../../../lib/battle/CUnitState.h" +#include "../../../lib/json/JsonNode.h" #include "mock/mock_UnitEnvironment.h" diff --git a/test/spells/effects/DispelTest.cpp b/test/spells/effects/DispelTest.cpp index 5d6f1db39..5d05ea81a 100644 --- a/test/spells/effects/DispelTest.cpp +++ b/test/spells/effects/DispelTest.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "EffectFixture.h" +#include "../../../lib/json/JsonNode.h" namespace test { diff --git a/test/spells/effects/EffectFixture.h b/test/spells/effects/EffectFixture.h index dde8e9a2d..4a2d38034 100644 --- a/test/spells/effects/EffectFixture.h +++ b/test/spells/effects/EffectFixture.h @@ -33,7 +33,6 @@ #include "../../mock/mock_ServerCallback.h" -#include "../../../lib/JsonNode.h" #include "../../../lib/battle/CBattleInfoCallback.h" namespace battle diff --git a/test/spells/effects/HealTest.cpp b/test/spells/effects/HealTest.cpp index af24fb1b7..d2479951c 100644 --- a/test/spells/effects/HealTest.cpp +++ b/test/spells/effects/HealTest.cpp @@ -14,6 +14,7 @@ #include #include "../../../lib/battle/CUnitState.h" +#include "../../../lib/json/JsonNode.h" #include "mock/mock_UnitEnvironment.h" diff --git a/test/spells/effects/SacrificeTest.cpp b/test/spells/effects/SacrificeTest.cpp index a8a4ebf62..2bde689ef 100644 --- a/test/spells/effects/SacrificeTest.cpp +++ b/test/spells/effects/SacrificeTest.cpp @@ -14,6 +14,7 @@ #include #include "../../../lib/battle/CUnitState.h" +#include "../../../lib/json/JsonNode.h" #include "mock/mock_UnitEnvironment.h" diff --git a/test/spells/effects/SummonTest.cpp b/test/spells/effects/SummonTest.cpp index cdd20af1c..2dfbfc659 100644 --- a/test/spells/effects/SummonTest.cpp +++ b/test/spells/effects/SummonTest.cpp @@ -14,6 +14,7 @@ #include #include "../../../lib/CCreatureHandler.h" +#include "../../../lib/json/JsonNode.h" namespace test { diff --git a/test/spells/effects/TeleportTest.cpp b/test/spells/effects/TeleportTest.cpp index e8b597689..00f46bfd1 100644 --- a/test/spells/effects/TeleportTest.cpp +++ b/test/spells/effects/TeleportTest.cpp @@ -11,6 +11,7 @@ #include "EffectFixture.h" +#include "../../../lib/json/JsonNode.h" #include namespace test diff --git a/test/spells/effects/TimedTest.cpp b/test/spells/effects/TimedTest.cpp index 01d9a2d10..e96f3d476 100644 --- a/test/spells/effects/TimedTest.cpp +++ b/test/spells/effects/TimedTest.cpp @@ -12,7 +12,8 @@ #include "EffectFixture.h" #include -#include "lib/modding/ModScope.h" +#include "../../../lib/modding/ModScope.h" +#include "../../../lib/json/JsonNode.h" namespace test { From 0b7bf565978403886df8e8133d7214ef91129cc1 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 14 Feb 2024 13:21:38 +0200 Subject: [PATCH 115/250] Remove old files --- lib/JsonDetail.cpp | 1268 ---------------------------------------- lib/JsonDetail.h | 133 ----- lib/json/JsonUtils.cpp | 4 + lib/json/JsonUtils.h | 4 + lobby/LobbyServer.cpp | 2 +- 5 files changed, 9 insertions(+), 1402 deletions(-) delete mode 100644 lib/JsonDetail.cpp delete mode 100644 lib/JsonDetail.h diff --git a/lib/JsonDetail.cpp b/lib/JsonDetail.cpp deleted file mode 100644 index ab2d4e6da..000000000 --- a/lib/JsonDetail.cpp +++ /dev/null @@ -1,1268 +0,0 @@ -/* - * JsonDetail.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 "JsonDetail.h" - -#include "VCMI_Lib.h" -#include "TextOperations.h" - -#include "filesystem/Filesystem.h" -#include "modding/ModScope.h" -#include "modding/CModHandler.h" -#include "ScopeGuard.h" - -VCMI_LIB_NAMESPACE_BEGIN - -static const JsonNode nullNode; - -template -void JsonWriter::writeContainer(Iterator begin, Iterator end) -{ - if (begin == end) - return; - - prefix += '\t'; - - writeEntry(begin++); - - while (begin != end) - { - out << (compactMode ? ", " : ",\n"); - writeEntry(begin++); - } - - out << (compactMode ? "" : "\n"); - prefix.resize(prefix.size()-1); -} - -void JsonWriter::writeEntry(JsonMap::const_iterator entry) -{ - if(!compactMode) - { - if (!entry->second.meta.empty()) - out << prefix << " // " << entry->second.meta << "\n"; - if(!entry->second.flags.empty()) - out << prefix << " // flags: " << boost::algorithm::join(entry->second.flags, ", ") << "\n"; - out << prefix; - } - writeString(entry->first); - out << " : "; - writeNode(entry->second); -} - -void JsonWriter::writeEntry(JsonVector::const_iterator entry) -{ - if(!compactMode) - { - if (!entry->meta.empty()) - out << prefix << " // " << entry->meta << "\n"; - if(!entry->flags.empty()) - out << prefix << " // flags: " << boost::algorithm::join(entry->flags, ", ") << "\n"; - out << prefix; - } - writeNode(*entry); -} - -void JsonWriter::writeString(const std::string &string) -{ - static const std::string escaped = "\"\\\b\f\n\r\t/"; - - static const std::array escaped_code = {'\"', '\\', 'b', 'f', 'n', 'r', 't', '/'}; - - out <<'\"'; - size_t pos = 0; - size_t start = 0; - for (; poswarn("File %s is not a valid JSON file!", fileName); - logMod->warn(errors); - } - return root; -} - -bool JsonParser::isValid() -{ - return errors.empty(); -} - -bool JsonParser::extractSeparator() -{ - if (!extractWhitespace()) - return false; - - if ( input[pos] !=':') - return error("Separator expected"); - - pos++; - return true; -} - -bool JsonParser::extractValue(JsonNode &node) -{ - if (!extractWhitespace()) - return false; - - switch (input[pos]) - { - case '\"': return extractString(node); - case 'n' : return extractNull(node); - case 't' : return extractTrue(node); - case 'f' : return extractFalse(node); - case '{' : return extractStruct(node); - case '[' : return extractArray(node); - case '-' : return extractFloat(node); - default: - { - if (input[pos] >= '0' && input[pos] <= '9') - return extractFloat(node); - return error("Value expected!"); - } - } -} - -bool JsonParser::extractWhitespace(bool verbose) -{ - while (true) - { - while(pos < input.size() && static_cast(input[pos]) <= ' ') - { - if (input[pos] == '\n') - { - lineCount++; - lineStart = pos+1; - } - pos++; - } - if (pos >= input.size() || input[pos] != '/') - break; - - pos++; - if (pos == input.size()) - break; - if (input[pos] == '/') - pos++; - else - error("Comments must consist of two slashes!", true); - - while (pos < input.size() && input[pos] != '\n') - pos++; - } - - if (pos >= input.size() && verbose) - return error("Unexpected end of file!"); - return true; -} - -bool JsonParser::extractEscaping(std::string &str) -{ - switch(input[pos]) - { - break; case '\"': str += '\"'; - break; case '\\': str += '\\'; - break; case 'b': str += '\b'; - break; case 'f': str += '\f'; - break; case 'n': str += '\n'; - break; case 'r': str += '\r'; - break; case 't': str += '\t'; - break; case '/': str += '/'; - break; default: return error("Unknown escape sequence!", true); - } - return true; -} - -bool JsonParser::extractString(std::string &str) -{ - if (input[pos] != '\"') - return error("String expected!"); - pos++; - - size_t first = pos; - - while (pos != input.size()) - { - if (input[pos] == '\"') // Correct end of string - { - str.append( &input[first], pos-first); - pos++; - return true; - } - if (input[pos] == '\\') // Escaping - { - str.append( &input[first], pos-first); - pos++; - if (pos == input.size()) - break; - extractEscaping(str); - first = pos + 1; - } - if (input[pos] == '\n') // end-of-line - { - str.append( &input[first], pos-first); - return error("Closing quote not found!", true); - } - if(static_cast(input[pos]) < ' ') // control character - { - str.append( &input[first], pos-first); - first = pos+1; - error("Illegal character in the string!", true); - } - pos++; - } - return error("Unterminated string!"); -} - -bool JsonParser::extractString(JsonNode &node) -{ - std::string str; - if (!extractString(str)) - return false; - - node.setType(JsonNode::JsonType::DATA_STRING); - node.String() = str; - return true; -} - -bool JsonParser::extractLiteral(const std::string &literal) -{ - if (literal.compare(0, literal.size(), &input[pos], literal.size()) != 0) - { - while (pos < input.size() && ((input[pos]>'a' && input[pos]<'z') - || (input[pos]>'A' && input[pos]<'Z'))) - pos++; - return error("Unknown literal found", true); - } - - pos += literal.size(); - return true; -} - -bool JsonParser::extractNull(JsonNode &node) -{ - if (!extractLiteral("null")) - return false; - - node.clear(); - return true; -} - -bool JsonParser::extractTrue(JsonNode &node) -{ - if (!extractLiteral("true")) - return false; - - node.Bool() = true; - return true; -} - -bool JsonParser::extractFalse(JsonNode &node) -{ - if (!extractLiteral("false")) - return false; - - node.Bool() = false; - return true; -} - -bool JsonParser::extractStruct(JsonNode &node) -{ - node.setType(JsonNode::JsonType::DATA_STRUCT); - pos++; - - if (!extractWhitespace()) - return false; - - //Empty struct found - if (input[pos] == '}') - { - pos++; - return true; - } - - while (true) - { - if (!extractWhitespace()) - return false; - - std::string key; - if (!extractString(key)) - return false; - - // split key string into actual key and meta-flags - std::vector keyAndFlags; - boost::split(keyAndFlags, key, boost::is_any_of("#")); - key = keyAndFlags[0]; - // check for unknown flags - helps with debugging - std::vector knownFlags = { "override" }; - for(int i = 1; i < keyAndFlags.size(); i++) - { - if(!vstd::contains(knownFlags, keyAndFlags[i])) - error("Encountered unknown flag #" + keyAndFlags[i], true); - } - - if (node.Struct().find(key) != node.Struct().end()) - error("Duplicate element encountered!", true); - - if (!extractSeparator()) - return false; - - if (!extractElement(node.Struct()[key], '}')) - return false; - - // flags from key string belong to referenced element - for(int i = 1; i < keyAndFlags.size(); i++) - node.Struct()[key].flags.push_back(keyAndFlags[i]); - - if (input[pos] == '}') - { - pos++; - return true; - } - } -} - -bool JsonParser::extractArray(JsonNode &node) -{ - pos++; - node.setType(JsonNode::JsonType::DATA_VECTOR); - - if (!extractWhitespace()) - return false; - - //Empty array found - if (input[pos] == ']') - { - pos++; - return true; - } - - while (true) - { - //NOTE: currently 50% of time is this vector resizing. - //May be useful to use list during parsing and then swap() all items to vector - node.Vector().resize(node.Vector().size()+1); - - if (!extractElement(node.Vector().back(), ']')) - return false; - - if (input[pos] == ']') - { - pos++; - return true; - } - } -} - -bool JsonParser::extractElement(JsonNode &node, char terminator) -{ - if (!extractValue(node)) - return false; - - if (!extractWhitespace()) - return false; - - bool comma = (input[pos] == ','); - if (comma ) - { - pos++; - if (!extractWhitespace()) - return false; - } - - if (input[pos] == terminator) - { - //FIXME: MOD COMPATIBILITY: Too many of these right now, re-enable later - //if (comma) - //error("Extra comma found!", true); - return true; - } - - if (!comma) - error("Comma expected!", true); - - return true; -} - -bool JsonParser::extractFloat(JsonNode &node) -{ - assert(input[pos] == '-' || (input[pos] >= '0' && input[pos] <= '9')); - bool negative=false; - double result=0; - si64 integerPart = 0; - bool isFloat = false; - - if (input[pos] == '-') - { - pos++; - negative = true; - } - - if (input[pos] < '0' || input[pos] > '9') - return error("Number expected!"); - - //Extract integer part - while (input[pos] >= '0' && input[pos] <= '9') - { - integerPart = integerPart*10+(input[pos]-'0'); - pos++; - } - - result = static_cast(integerPart); - - if (input[pos] == '.') - { - //extract fractional part - isFloat = true; - pos++; - double fractMult = 0.1; - if (input[pos] < '0' || input[pos] > '9') - return error("Decimal part expected!"); - - while (input[pos] >= '0' && input[pos] <= '9') - { - result = result + fractMult*(input[pos]-'0'); - fractMult /= 10; - pos++; - } - } - - if(input[pos] == 'e') - { - //extract exponential part - pos++; - isFloat = true; - bool powerNegative = false; - double power = 0; - - if(input[pos] == '-') - { - pos++; - powerNegative = true; - } - else if(input[pos] == '+') - { - pos++; - } - - if (input[pos] < '0' || input[pos] > '9') - return error("Exponential part expected!"); - - while (input[pos] >= '0' && input[pos] <= '9') - { - power = power*10 + (input[pos]-'0'); - pos++; - } - - if(powerNegative) - power = -power; - - result *= std::pow(10, power); - } - - if(isFloat) - { - if(negative) - result = -result; - - node.setType(JsonNode::JsonType::DATA_FLOAT); - node.Float() = result; - } - else - { - if(negative) - integerPart = -integerPart; - - node.setType(JsonNode::JsonType::DATA_INTEGER); - node.Integer() = integerPart; - } - - return true; -} - -bool JsonParser::error(const std::string &message, bool warning) -{ - std::ostringstream stream; - std::string type(warning?" warning: ":" error: "); - - stream << "At line " << lineCount << ", position "< stringToType = -{ - {"null", JsonNode::JsonType::DATA_NULL}, - {"boolean", JsonNode::JsonType::DATA_BOOL}, - {"number", JsonNode::JsonType::DATA_FLOAT}, - {"string", JsonNode::JsonType::DATA_STRING}, - {"array", JsonNode::JsonType::DATA_VECTOR}, - {"object", JsonNode::JsonType::DATA_STRUCT} -}; - -namespace -{ - namespace Common - { - std::string emptyCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - // check is not needed - e.g. incorporated into another check - return ""; - } - - std::string notImplementedCheck(Validation::ValidationData & validator, - const JsonNode & baseSchema, - const JsonNode & schema, - const JsonNode & data) - { - return "Not implemented entry in schema"; - } - - std::string schemaListCheck(Validation::ValidationData & validator, - const JsonNode & baseSchema, - const JsonNode & schema, - const JsonNode & data, - const std::string & errorMsg, - const std::function & isValid) - { - std::string errors = "\n"; - size_t result = 0; - - for(const auto & schemaEntry : schema.Vector()) - { - std::string error = check(schemaEntry, data, validator); - if (error.empty()) - { - result++; - } - else - { - errors += error; - errors += "\n"; - } - } - if (isValid(result)) - return ""; - else - return validator.makeErrorMessage(errorMsg) + errors; - } - - std::string allOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass all schemas", [&](size_t count) - { - return count == schema.Vector().size(); - }); - } - - std::string anyOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass any schema", [&](size_t count) - { - return count > 0; - }); - } - - std::string oneOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass exactly one schema", [&](size_t count) - { - return count == 1; - }); - } - - std::string notCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (check(schema, data, validator).empty()) - return validator.makeErrorMessage("Successful validation against negative check"); - return ""; - } - - std::string enumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - for(const auto & enumEntry : schema.Vector()) - { - if (data == enumEntry) - return ""; - } - return validator.makeErrorMessage("Key must have one of predefined values"); - } - - std::string typeCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - const auto & typeName = schema.String(); - auto it = stringToType.find(typeName); - if(it == stringToType.end()) - { - return validator.makeErrorMessage("Unknown type in schema:" + typeName); - } - - JsonNode::JsonType type = it->second; - - //FIXME: hack for integer values - if(data.isNumber() && type == JsonNode::JsonType::DATA_FLOAT) - return ""; - - if(type != data.getType() && data.getType() != JsonNode::JsonType::DATA_NULL) - return validator.makeErrorMessage("Type mismatch! Expected " + schema.String()); - return ""; - } - - std::string refCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string URI = schema.String(); - //node must be validated using schema pointed by this reference and not by data here - //Local reference. Turn it into more easy to handle remote ref - if (boost::algorithm::starts_with(URI, "#")) - { - const std::string name = validator.usedSchemas.back(); - const std::string nameClean = name.substr(0, name.find('#')); - URI = nameClean + URI; - } - return check(URI, data, validator); - } - - std::string formatCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - auto formats = Validation::getKnownFormats(); - std::string errors; - auto checker = formats.find(schema.String()); - if (checker != formats.end()) - { - if (data.isString()) - { - std::string result = checker->second(data); - if (!result.empty()) - errors += validator.makeErrorMessage(result); - } - else - { - errors += validator.makeErrorMessage("Format value must be string: " + schema.String()); - } - } - else - errors += validator.makeErrorMessage("Unsupported format type: " + schema.String()); - return errors; - } - } - - namespace String - { - std::string maxLengthCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (data.String().size() > schema.Float()) - return validator.makeErrorMessage((boost::format("String is longer than %d symbols") % schema.Float()).str()); - return ""; - } - - std::string minLengthCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (data.String().size() < schema.Float()) - return validator.makeErrorMessage((boost::format("String is shorter than %d symbols") % schema.Float()).str()); - return ""; - } - } - - namespace Number - { - - std::string maximumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (baseSchema["exclusiveMaximum"].Bool()) - { - if (data.Float() >= schema.Float()) - return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str()); - } - else - { - if (data.Float() > schema.Float()) - return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str()); - } - return ""; - } - - std::string minimumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (baseSchema["exclusiveMinimum"].Bool()) - { - if (data.Float() <= schema.Float()) - return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str()); - } - else - { - if (data.Float() < schema.Float()) - return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str()); - } - return ""; - } - - std::string multipleOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - double result = data.Float() / schema.Float(); - if (floor(result) != result) - return validator.makeErrorMessage((boost::format("Value is not divisible by %d") % schema.Float()).str()); - return ""; - } - } - - namespace Vector - { - std::string itemEntryCheck(Validation::ValidationData & validator, const JsonVector & items, const JsonNode & schema, size_t index) - { - validator.currentPath.emplace_back(); - validator.currentPath.back().Float() = static_cast(index); - auto onExit = vstd::makeScopeGuard([&]() - { - validator.currentPath.pop_back(); - }); - - if (!schema.isNull()) - return check(schema, items[index], validator); - return ""; - } - - std::string itemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string errors; - for (size_t i=0; i i) - errors += itemEntryCheck(validator, data.Vector(), schema.Vector()[i], i); - } - else - { - errors += itemEntryCheck(validator, data.Vector(), schema, i); - } - } - return errors; - } - - std::string additionalItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string errors; - // "items" is struct or empty (defaults to empty struct) - validation always successful - const JsonNode & items = baseSchema["items"]; - if (items.getType() != JsonNode::JsonType::DATA_VECTOR) - return ""; - - for (size_t i=items.Vector().size(); i schema.Float()) - return validator.makeErrorMessage((boost::format("Length is bigger than %d") % schema.Float()).str()); - return ""; - } - - std::string uniqueItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (schema.Bool()) - { - for (auto itA = schema.Vector().begin(); itA != schema.Vector().end(); itA++) - { - auto itB = itA; - while (++itB != schema.Vector().end()) - { - if (*itA == *itB) - return validator.makeErrorMessage("List must consist from unique items"); - } - } - } - return ""; - } - } - - namespace Struct - { - std::string maxPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (data.Struct().size() > schema.Float()) - return validator.makeErrorMessage((boost::format("Number of entries is bigger than %d") % schema.Float()).str()); - return ""; - } - - std::string minPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (data.Struct().size() < schema.Float()) - return validator.makeErrorMessage((boost::format("Number of entries is less than %d") % schema.Float()).str()); - return ""; - } - - std::string uniquePropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - for (auto itA = data.Struct().begin(); itA != data.Struct().end(); itA++) - { - auto itB = itA; - while (++itB != data.Struct().end()) - { - if (itA->second == itB->second) - return validator.makeErrorMessage("List must consist from unique items"); - } - } - return ""; - } - - std::string requiredCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string errors; - for(const auto & required : schema.Vector()) - { - if (data[required.String()].isNull()) - errors += validator.makeErrorMessage("Required entry " + required.String() + " is missing"); - } - return errors; - } - - std::string dependenciesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string errors; - for(const auto & deps : schema.Struct()) - { - if (!data[deps.first].isNull()) - { - if (deps.second.getType() == JsonNode::JsonType::DATA_VECTOR) - { - JsonVector depList = deps.second.Vector(); - for(auto & depEntry : depList) - { - if (data[depEntry.String()].isNull()) - errors += validator.makeErrorMessage("Property " + depEntry.String() + " required for " + deps.first + " is missing"); - } - } - else - { - if (!check(deps.second, data, validator).empty()) - errors += validator.makeErrorMessage("Requirements for " + deps.first + " are not fulfilled"); - } - } - } - return errors; - } - - std::string propertyEntryCheck(Validation::ValidationData & validator, const JsonNode &node, const JsonNode & schema, const std::string & nodeName) - { - validator.currentPath.emplace_back(); - validator.currentPath.back().String() = nodeName; - auto onExit = vstd::makeScopeGuard([&]() - { - validator.currentPath.pop_back(); - }); - - // there is schema specifically for this item - if (!schema.isNull()) - return check(schema, node, validator); - return ""; - } - - std::string propertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string errors; - - for(const auto & entry : data.Struct()) - errors += propertyEntryCheck(validator, entry.second, schema[entry.first], entry.first); - return errors; - } - - std::string additionalPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string errors; - for(const auto & entry : data.Struct()) - { - if (baseSchema["properties"].Struct().count(entry.first) == 0) - { - // try generic additionalItems schema - if (schema.getType() == JsonNode::JsonType::DATA_STRUCT) - errors += propertyEntryCheck(validator, entry.second, schema, entry.first); - - // or, additionalItems field can be bool which indicates if such items are allowed - else if(!schema.isNull() && !schema.Bool()) // present and set to false - error - errors += validator.makeErrorMessage("Unknown entry found: " + entry.first); - } - } - return errors; - } - } - - namespace Formats - { - bool testFilePresence(const std::string & scope, const ResourcePath & resource) - { - std::set allowedScopes; - if(scope != ModScope::scopeBuiltin() && !scope.empty()) // all real mods may have dependencies - { - //NOTE: recursive dependencies are not allowed at the moment - update code if this changes - bool found = true; - allowedScopes = VLC->modh->getModDependencies(scope, found); - - if(!found) - return false; - - allowedScopes.insert(ModScope::scopeBuiltin()); // all mods can use H3 files - } - allowedScopes.insert(scope); // mods can use their own files - - for(const auto & entry : allowedScopes) - { - if (CResourceHandler::get(entry)->existsResource(resource)) - return true; - } - return false; - } - - #define TEST_FILE(scope, prefix, file, type) \ - if (testFilePresence(scope, ResourcePath(prefix + file, type))) \ - return "" - - std::string testAnimation(const std::string & path, const std::string & scope) - { - TEST_FILE(scope, "Sprites/", path, EResType::ANIMATION); - TEST_FILE(scope, "Sprites/", path, EResType::JSON); - return "Animation file \"" + path + "\" was not found"; - } - - std::string textFile(const JsonNode & node) - { - TEST_FILE(node.meta, "", node.String(), EResType::JSON); - return "Text file \"" + node.String() + "\" was not found"; - } - - std::string musicFile(const JsonNode & node) - { - TEST_FILE(node.meta, "Music/", node.String(), EResType::SOUND); - TEST_FILE(node.meta, "", node.String(), EResType::SOUND); - return "Music file \"" + node.String() + "\" was not found"; - } - - std::string soundFile(const JsonNode & node) - { - TEST_FILE(node.meta, "Sounds/", node.String(), EResType::SOUND); - return "Sound file \"" + node.String() + "\" was not found"; - } - - std::string defFile(const JsonNode & node) - { - return testAnimation(node.String(), node.meta); - } - - std::string animationFile(const JsonNode & node) - { - return testAnimation(node.String(), node.meta); - } - - std::string imageFile(const JsonNode & node) - { - TEST_FILE(node.meta, "Data/", node.String(), EResType::IMAGE); - TEST_FILE(node.meta, "Sprites/", node.String(), EResType::IMAGE); - if (node.String().find(':') != std::string::npos) - return testAnimation(node.String().substr(0, node.String().find(':')), node.meta); - return "Image file \"" + node.String() + "\" was not found"; - } - - std::string videoFile(const JsonNode & node) - { - TEST_FILE(node.meta, "Video/", node.String(), EResType::VIDEO); - return "Video file \"" + node.String() + "\" was not found"; - } - - #undef TEST_FILE - } - - Validation::TValidatorMap createCommonFields() - { - Validation::TValidatorMap ret; - - ret["format"] = Common::formatCheck; - ret["allOf"] = Common::allOfCheck; - ret["anyOf"] = Common::anyOfCheck; - ret["oneOf"] = Common::oneOfCheck; - ret["enum"] = Common::enumCheck; - ret["type"] = Common::typeCheck; - ret["not"] = Common::notCheck; - ret["$ref"] = Common::refCheck; - - // fields that don't need implementation - ret["title"] = Common::emptyCheck; - ret["$schema"] = Common::emptyCheck; - ret["default"] = Common::emptyCheck; - ret["description"] = Common::emptyCheck; - ret["definitions"] = Common::emptyCheck; - return ret; - } - - Validation::TValidatorMap createStringFields() - { - Validation::TValidatorMap ret = createCommonFields(); - ret["maxLength"] = String::maxLengthCheck; - ret["minLength"] = String::minLengthCheck; - - ret["pattern"] = Common::notImplementedCheck; - return ret; - } - - Validation::TValidatorMap createNumberFields() - { - Validation::TValidatorMap ret = createCommonFields(); - ret["maximum"] = Number::maximumCheck; - ret["minimum"] = Number::minimumCheck; - ret["multipleOf"] = Number::multipleOfCheck; - - ret["exclusiveMaximum"] = Common::emptyCheck; - ret["exclusiveMinimum"] = Common::emptyCheck; - return ret; - } - - Validation::TValidatorMap createVectorFields() - { - Validation::TValidatorMap ret = createCommonFields(); - ret["items"] = Vector::itemsCheck; - ret["minItems"] = Vector::minItemsCheck; - ret["maxItems"] = Vector::maxItemsCheck; - ret["uniqueItems"] = Vector::uniqueItemsCheck; - ret["additionalItems"] = Vector::additionalItemsCheck; - return ret; - } - - Validation::TValidatorMap createStructFields() - { - Validation::TValidatorMap ret = createCommonFields(); - ret["additionalProperties"] = Struct::additionalPropertiesCheck; - ret["uniqueProperties"] = Struct::uniquePropertiesCheck; - ret["maxProperties"] = Struct::maxPropertiesCheck; - ret["minProperties"] = Struct::minPropertiesCheck; - ret["dependencies"] = Struct::dependenciesCheck; - ret["properties"] = Struct::propertiesCheck; - ret["required"] = Struct::requiredCheck; - - ret["patternProperties"] = Common::notImplementedCheck; - return ret; - } - - Validation::TFormatMap createFormatMap() - { - Validation::TFormatMap ret; - ret["textFile"] = Formats::textFile; - ret["musicFile"] = Formats::musicFile; - ret["soundFile"] = Formats::soundFile; - ret["defFile"] = Formats::defFile; - ret["animationFile"] = Formats::animationFile; - ret["imageFile"] = Formats::imageFile; - ret["videoFile"] = Formats::videoFile; - - return ret; - } -} - -namespace Validation -{ - std::string ValidationData::makeErrorMessage(const std::string &message) - { - std::string errors; - errors += "At "; - if (!currentPath.empty()) - { - for(const JsonNode &path : currentPath) - { - errors += "/"; - if (path.getType() == JsonNode::JsonType::DATA_STRING) - errors += path.String(); - else - errors += std::to_string(static_cast(path.Float())); - } - } - else - errors += ""; - errors += "\n\t Error: " + message + "\n"; - return errors; - } - - std::string check(const std::string & schemaName, const JsonNode & data) - { - ValidationData validator; - return check(schemaName, data, validator); - } - - std::string check(const std::string & schemaName, const JsonNode & data, ValidationData & validator) - { - validator.usedSchemas.push_back(schemaName); - auto onscopeExit = vstd::makeScopeGuard([&]() - { - validator.usedSchemas.pop_back(); - }); - return check(JsonUtils::getSchema(schemaName), data, validator); - } - - std::string check(const JsonNode & schema, const JsonNode & data, ValidationData & validator) - { - const TValidatorMap & knownFields = getKnownFieldsFor(data.getType()); - std::string errors; - for(const auto & entry : schema.Struct()) - { - auto checker = knownFields.find(entry.first); - if (checker != knownFields.end()) - errors += checker->second(validator, schema, entry.second, data); - //else - // errors += validator.makeErrorMessage("Unknown entry in schema " + entry.first); - } - return errors; - } - - const TValidatorMap & getKnownFieldsFor(JsonNode::JsonType type) - { - static const TValidatorMap commonFields = createCommonFields(); - static const TValidatorMap numberFields = createNumberFields(); - static const TValidatorMap stringFields = createStringFields(); - static const TValidatorMap vectorFields = createVectorFields(); - static const TValidatorMap structFields = createStructFields(); - - switch (type) - { - case JsonNode::JsonType::DATA_FLOAT: - case JsonNode::JsonType::DATA_INTEGER: - return numberFields; - case JsonNode::JsonType::DATA_STRING: return stringFields; - case JsonNode::JsonType::DATA_VECTOR: return vectorFields; - case JsonNode::JsonType::DATA_STRUCT: return structFields; - default: return commonFields; - } - } - - const TFormatMap & getKnownFormats() - { - static const TFormatMap knownFormats = createFormatMap(); - return knownFormats; - } - -} // Validation namespace - -VCMI_LIB_NAMESPACE_END diff --git a/lib/JsonDetail.h b/lib/JsonDetail.h deleted file mode 100644 index b817b8961..000000000 --- a/lib/JsonDetail.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - * JsonDetail.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 "JsonNode.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class JsonWriter -{ - //prefix for each line (tabulation) - std::string prefix; - std::ostream & out; - //sets whether compact nodes are written in single-line format - bool compact; - //tracks whether we are currently using single-line format - bool compactMode = false; -public: - template - void writeContainer(Iterator begin, Iterator end); - void writeEntry(JsonMap::const_iterator entry); - void writeEntry(JsonVector::const_iterator entry); - void writeString(const std::string & string); - void writeNode(const JsonNode & node); - JsonWriter(std::ostream & output, bool compact = false); -}; - -//Tiny string class that uses const char* as data for speed, members are private -//for ease of debugging and some compatibility with std::string -class constString -{ - const char *data; - const size_t datasize; - -public: - constString(const char * inputString, size_t stringSize): - data(inputString), - datasize(stringSize) - { - } - - inline size_t size() const - { - return datasize; - }; - - inline const char& operator[] (size_t position) - { - assert (position < datasize); - return data[position]; - } -}; - -//Internal class for string -> JsonNode conversion -class DLL_LINKAGE JsonParser -{ - std::string errors; // Contains description of all encountered errors - constString input; // Input data - ui32 lineCount; // Currently parsed line, starting from 1 - size_t lineStart; // Position of current line start - size_t pos; // Current position of parser - - //Helpers - bool extractEscaping(std::string &str); - bool extractLiteral(const std::string &literal); - bool extractString(std::string &string); - bool extractWhitespace(bool verbose = true); - bool extractSeparator(); - bool extractElement(JsonNode &node, char terminator); - - //Methods for extracting JSON data - bool extractArray(JsonNode &node); - bool extractFalse(JsonNode &node); - bool extractFloat(JsonNode &node); - bool extractNull(JsonNode &node); - bool extractString(JsonNode &node); - bool extractStruct(JsonNode &node); - bool extractTrue(JsonNode &node); - bool extractValue(JsonNode &node); - - //Add error\warning message to list - bool error(const std::string &message, bool warning=false); - -public: - JsonParser(const char * inputString, size_t stringSize); - - /// do actual parsing. filename is name of file that will printed to console if any errors were found - JsonNode parse(const std::string & fileName); - - /// returns true if parsing was successful - bool isValid(); -}; - -//Internal class for Json validation. Mostly compilant with json-schema v4 draft -namespace Validation -{ - /// struct used to pass data around during validation - struct ValidationData - { - /// path from root node to current one. - /// JsonNode is used as variant - either string (name of node) or as float (index in list) - std::vector currentPath; - - /// Stack of used schemas. Last schema is the one used currently. - /// May contain multiple items in case if remote references were found - std::vector usedSchemas; - - /// generates error message - std::string makeErrorMessage(const std::string &message); - }; - - using TFormatValidator = std::function; - using TFormatMap = std::unordered_map; - using TFieldValidator = std::function; - using TValidatorMap = std::unordered_map; - - /// map of known fields in schema - const TValidatorMap & getKnownFieldsFor(JsonNode::JsonType type); - const TFormatMap & getKnownFormats(); - - std::string check(const std::string & schemaName, const JsonNode & data); - std::string check(const std::string & schemaName, const JsonNode & data, ValidationData & validator); - std::string check(const JsonNode & schema, const JsonNode & data, ValidationData & validator); -} - -VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 51f8c9900..8d1cb7152 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -26,6 +26,8 @@ #include "../constants/StringConstants.h" #include "../battle/BattleHex.h" +VCMI_LIB_NAMESPACE_BEGIN + static const JsonNode nullNode; static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node) @@ -1237,3 +1239,5 @@ DLL_LINKAGE JsonNode JsonUtils::intNode(si64 value) node.Integer() = value; return node; } + +VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonUtils.h b/lib/json/JsonUtils.h index 2a733655e..ce1d88141 100644 --- a/lib/json/JsonUtils.h +++ b/lib/json/JsonUtils.h @@ -12,6 +12,8 @@ #include "JsonNode.h" #include "../GameConstants.h" +VCMI_LIB_NAMESPACE_BEGIN + namespace JsonUtils { DLL_LINKAGE std::shared_ptr parseBonus(const JsonVector & ability_vec); @@ -102,3 +104,5 @@ namespace JsonUtils DLL_LINKAGE JsonNode stringNode(const std::string & value); DLL_LINKAGE JsonNode intNode(si64 value); } + +VCMI_LIB_NAMESPACE_END diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 86d846e0e..56f8bba3d 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -12,7 +12,7 @@ #include "LobbyDatabase.h" -#include "../lib/JsonNode.h" +#include "../lib/json/JsonNode.h" #include #include From 3740f8b02fac62cb8ab64abf9faf820ae619d580 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 14 Feb 2024 15:48:06 +0200 Subject: [PATCH 116/250] Moved bonus parsing to a new file --- lib/BattleFieldHandler.cpp | 2 +- lib/CArtHandler.cpp | 2 +- lib/CCreatureHandler.cpp | 2 +- lib/CHeroHandler.cpp | 1 + lib/CMakeLists.txt | 2 + lib/CSkillHandler.cpp | 1 + lib/CTownHandler.cpp | 2 +- lib/gameState/CGameState.cpp | 1 + lib/json/JsonBonus.cpp | 869 ++++++++++++++++++++++++++++++ lib/json/JsonBonus.h | 28 + lib/json/JsonRandom.cpp | 2 +- lib/json/JsonUtils.cpp | 836 ---------------------------- lib/json/JsonUtils.h | 9 - lib/mapObjects/CGHeroInstance.cpp | 2 +- lib/serializer/JsonUpdater.cpp | 2 +- lib/spells/CSpellHandler.cpp | 1 + lib/spells/TargetCondition.cpp | 2 +- lib/spells/effects/Moat.cpp | 2 +- lib/spells/effects/Timed.cpp | 2 +- 19 files changed, 913 insertions(+), 855 deletions(-) create mode 100644 lib/json/JsonBonus.cpp create mode 100644 lib/json/JsonBonus.h diff --git a/lib/BattleFieldHandler.cpp b/lib/BattleFieldHandler.cpp index 672e7cf12..4d1eb5591 100644 --- a/lib/BattleFieldHandler.cpp +++ b/lib/BattleFieldHandler.cpp @@ -11,7 +11,7 @@ #include #include "BattleFieldHandler.h" -#include "json/JsonUtils.h" +#include "json/JsonBonus.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index bbe046d43..6a205217a 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -15,7 +15,7 @@ #include "GameSettings.h" #include "mapObjects/MapObjects.h" #include "constants/StringConstants.h" -#include "json/JsonUtils.h" +#include "json/JsonBonus.h" #include "mapObjectConstructors/AObjectTypeHandler.h" #include "mapObjectConstructors/CObjectClassesHandler.h" #include "serializer/JsonSerializeFormat.h" diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 8fe85a7a1..6779c8289 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -20,7 +20,7 @@ #include "constants/StringConstants.h" #include "bonuses/Limiters.h" #include "bonuses/Updaters.h" -#include "json/JsonUtils.h" +#include "json/JsonBonus.h" #include "serializer/JsonDeserializer.h" #include "serializer/JsonUpdater.h" #include "mapObjectConstructors/AObjectTypeHandler.h" diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 413d10fe6..5f96c9999 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -23,6 +23,7 @@ #include "BattleFieldHandler.h" #include "bonuses/Limiters.h" #include "bonuses/Updaters.h" +#include "json/JsonBonus.h" #include "json/JsonUtils.h" #include "mapObjectConstructors/AObjectTypeHandler.h" #include "mapObjectConstructors/CObjectClassesHandler.h" diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a74e3d195..68838759d 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -61,6 +61,7 @@ set(lib_SRCS filesystem/MinizipExtensions.cpp filesystem/ResourcePath.cpp + json/JsonBonus.cpp json/JsonNode.cpp json/JsonParser.cpp json/JsonRandom.cpp @@ -403,6 +404,7 @@ set(lib_HEADERS filesystem/MinizipExtensions.h filesystem/ResourcePath.h + json/JsonBonus.h json/JsonNode.h json/JsonParser.h json/JsonRandom.h diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index f6fa25fe9..3c8a253e8 100644 --- a/lib/CSkillHandler.cpp +++ b/lib/CSkillHandler.cpp @@ -16,6 +16,7 @@ #include "CGeneralTextHandler.h" #include "filesystem/Filesystem.h" +#include "json/JsonBonus.h" #include "json/JsonUtils.h" #include "modding/IdentifierStorage.h" #include "modding/ModUtility.h" diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index a540a7c71..df3e33f57 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -22,7 +22,7 @@ #include "filesystem/Filesystem.h" #include "bonuses/Bonus.h" #include "bonuses/Propagators.h" -#include "json/JsonUtils.h" +#include "json/JsonBonus.h" #include "ResourceSet.h" #include "mapObjectConstructors/AObjectTypeHandler.h" #include "mapObjectConstructors/CObjectClassesHandler.h" diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index ce1d0b97f..9ebac6cbb 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -31,6 +31,7 @@ #include "../campaign/CampaignState.h" #include "../constants/StringConstants.h" #include "../filesystem/ResourcePath.h" +#include "../json/JsonBonus.h" #include "../json/JsonUtils.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" diff --git a/lib/json/JsonBonus.cpp b/lib/json/JsonBonus.cpp new file mode 100644 index 000000000..ffc540ff8 --- /dev/null +++ b/lib/json/JsonBonus.cpp @@ -0,0 +1,869 @@ +/* + * JsonUtils.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 "JsonBonus.h" + +#include "JsonValidator.h" + +#include "../ScopeGuard.h" +#include "../bonuses/BonusParams.h" +#include "../bonuses/Bonus.h" +#include "../bonuses/Limiters.h" +#include "../bonuses/Propagators.h" +#include "../bonuses/Updaters.h" +#include "../filesystem/Filesystem.h" +#include "../modding/IdentifierStorage.h" +#include "../VCMI_Lib.h" //for identifier resolution +#include "../CGeneralTextHandler.h" +#include "../constants/StringConstants.h" +#include "../battle/BattleHex.h" + +VCMI_LIB_NAMESPACE_BEGIN + +static const JsonNode nullNode; + +static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node) +{ + if (node.isNull()) + { + subtype = BonusSubtypeID(); + return; + } + + if (node.isNumber()) // Compatibility code for 1.3 or older + { + logMod->warn("Bonus subtype must be string! (%s)", node.meta); + subtype = BonusCustomSubtype(node.Integer()); + return; + } + + if (!node.isString()) + { + logMod->warn("Bonus subtype must be string! (%s)", node.meta); + subtype = BonusSubtypeID(); + return; + } + + switch (type) + { + case BonusType::MAGIC_SCHOOL_SKILL: + case BonusType::SPELL_DAMAGE: + case BonusType::SPELLS_OF_SCHOOL: + case BonusType::SPELL_DAMAGE_REDUCTION: + case BonusType::SPELL_SCHOOL_IMMUNITY: + case BonusType::NEGATIVE_EFFECTS_IMMUNITY: + { + VLC->identifiers()->requestIdentifier( "spellSchool", node, [&subtype](int32_t identifier) + { + subtype = SpellSchool(identifier); + }); + break; + } + case BonusType::NO_TERRAIN_PENALTY: + { + VLC->identifiers()->requestIdentifier( "terrain", node, [&subtype](int32_t identifier) + { + subtype = TerrainId(identifier); + }); + break; + } + case BonusType::PRIMARY_SKILL: + { + VLC->identifiers()->requestIdentifier( "primarySkill", node, [&subtype](int32_t identifier) + { + subtype = PrimarySkill(identifier); + }); + break; + } + case BonusType::IMPROVED_NECROMANCY: + case BonusType::HERO_GRANTS_ATTACKS: + case BonusType::BONUS_DAMAGE_CHANCE: + case BonusType::BONUS_DAMAGE_PERCENTAGE: + case BonusType::SPECIAL_UPGRADE: + case BonusType::HATE: + case BonusType::SUMMON_GUARDIANS: + case BonusType::MANUAL_CONTROL: + { + VLC->identifiers()->requestIdentifier( "creature", node, [&subtype](int32_t identifier) + { + subtype = CreatureID(identifier); + }); + break; + } + case BonusType::SPELL_IMMUNITY: + case BonusType::SPELL_DURATION: + case BonusType::SPECIAL_ADD_VALUE_ENCHANT: + case BonusType::SPECIAL_FIXED_VALUE_ENCHANT: + case BonusType::SPECIAL_PECULIAR_ENCHANT: + case BonusType::SPECIAL_SPELL_LEV: + case BonusType::SPECIFIC_SPELL_DAMAGE: + case BonusType::SPELL: + case BonusType::OPENING_BATTLE_SPELL: + case BonusType::SPELL_LIKE_ATTACK: + case BonusType::CATAPULT: + case BonusType::CATAPULT_EXTRA_SHOTS: + case BonusType::HEALER: + case BonusType::SPELLCASTER: + case BonusType::ENCHANTER: + case BonusType::SPELL_AFTER_ATTACK: + case BonusType::SPELL_BEFORE_ATTACK: + case BonusType::SPECIFIC_SPELL_POWER: + case BonusType::ENCHANTED: + case BonusType::MORE_DAMAGE_FROM_SPELL: + case BonusType::NOT_ACTIVE: + { + VLC->identifiers()->requestIdentifier( "spell", node, [&subtype](int32_t identifier) + { + subtype = SpellID(identifier); + }); + break; + } + case BonusType::GENERATE_RESOURCE: + { + VLC->identifiers()->requestIdentifier( "resource", node, [&subtype](int32_t identifier) + { + subtype = GameResID(identifier); + }); + break; + } + case BonusType::MOVEMENT: + case BonusType::WATER_WALKING: + case BonusType::FLYING_MOVEMENT: + case BonusType::NEGATE_ALL_NATURAL_IMMUNITIES: + case BonusType::CREATURE_DAMAGE: + case BonusType::FLYING: + case BonusType::FIRST_STRIKE: + case BonusType::GENERAL_DAMAGE_REDUCTION: + case BonusType::PERCENTAGE_DAMAGE_BOOST: + case BonusType::SOUL_STEAL: + case BonusType::TRANSMUTATION: + case BonusType::DESTRUCTION: + case BonusType::DEATH_STARE: + case BonusType::REBIRTH: + case BonusType::VISIONS: + case BonusType::SPELLS_OF_LEVEL: // spell level + case BonusType::CREATURE_GROWTH: // creature level + { + VLC->identifiers()->requestIdentifier( "bonusSubtype", node, [&subtype](int32_t identifier) + { + subtype = BonusCustomSubtype(identifier); + }); + break; + } + default: + for(const auto & i : bonusNameMap) + if(i.second == type) + logMod->warn("Bonus type %s does not supports subtypes!", i.first ); + subtype = BonusSubtypeID(); + } +} + +static void loadBonusSourceInstance(BonusSourceID & sourceInstance, BonusSource sourceType, const JsonNode & node) +{ + if (node.isNull()) + { + sourceInstance = BonusCustomSource(); + return; + } + + if (node.isNumber()) // Compatibility code for 1.3 or older + { + logMod->warn("Bonus source must be string!"); + sourceInstance = BonusCustomSource(node.Integer()); + return; + } + + if (!node.isString()) + { + logMod->warn("Bonus source must be string!"); + sourceInstance = BonusCustomSource(); + return; + } + + switch (sourceType) + { + case BonusSource::ARTIFACT: + case BonusSource::ARTIFACT_INSTANCE: + { + VLC->identifiers()->requestIdentifier( "artifact", node, [&sourceInstance](int32_t identifier) + { + sourceInstance = ArtifactID(identifier); + }); + break; + } + case BonusSource::OBJECT_TYPE: + { + VLC->identifiers()->requestIdentifier( "object", node, [&sourceInstance](int32_t identifier) + { + sourceInstance = Obj(identifier); + }); + break; + } + case BonusSource::OBJECT_INSTANCE: + case BonusSource::HERO_BASE_SKILL: + sourceInstance = ObjectInstanceID(ObjectInstanceID::decode(node.String())); + break; + case BonusSource::CREATURE_ABILITY: + { + VLC->identifiers()->requestIdentifier( "creature", node, [&sourceInstance](int32_t identifier) + { + sourceInstance = CreatureID(identifier); + }); + break; + } + case BonusSource::TERRAIN_OVERLAY: + { + VLC->identifiers()->requestIdentifier( "spell", node, [&sourceInstance](int32_t identifier) + { + sourceInstance = BattleField(identifier); + }); + break; + } + case BonusSource::SPELL_EFFECT: + { + VLC->identifiers()->requestIdentifier( "spell", node, [&sourceInstance](int32_t identifier) + { + sourceInstance = SpellID(identifier); + }); + break; + } + case BonusSource::TOWN_STRUCTURE: + assert(0); // TODO + sourceInstance = BuildingTypeUniqueID(); + break; + case BonusSource::SECONDARY_SKILL: + { + VLC->identifiers()->requestIdentifier( "secondarySkill", node, [&sourceInstance](int32_t identifier) + { + sourceInstance = SecondarySkill(identifier); + }); + break; + } + case BonusSource::HERO_SPECIAL: + { + VLC->identifiers()->requestIdentifier( "hero", node, [&sourceInstance](int32_t identifier) + { + sourceInstance = HeroTypeID(identifier); + }); + break; + } + case BonusSource::CAMPAIGN_BONUS: + sourceInstance = CampaignScenarioID(CampaignScenarioID::decode(node.String())); + break; + case BonusSource::ARMY: + case BonusSource::STACK_EXPERIENCE: + case BonusSource::COMMANDER: + case BonusSource::GLOBAL: + case BonusSource::TERRAIN_NATIVE: + case BonusSource::OTHER: + default: + sourceInstance = BonusSourceID(); + break; + } +} + +std::shared_ptr JsonUtils::parseBonus(const JsonVector & ability_vec) +{ + auto b = std::make_shared(); + std::string type = ability_vec[0].String(); + auto it = bonusNameMap.find(type); + if (it == bonusNameMap.end()) + { + logMod->error("Error: invalid ability type %s.", type); + return b; + } + b->type = it->second; + + b->val = static_cast(ability_vec[1].Float()); + loadBonusSubtype(b->subtype, b->type, ability_vec[2]); + b->additionalInfo = static_cast(ability_vec[3].Float()); + b->duration = BonusDuration::PERMANENT; //TODO: handle flags (as integer) + b->turnsRemain = 0; + return b; +} + +template +const T parseByMap(const std::map & map, const JsonNode * val, const std::string & err) +{ + if (!val->isNull()) + { + const std::string & type = val->String(); + auto it = map.find(type); + if (it == map.end()) + { + logMod->error("Error: invalid %s%s.", err, type); + return {}; + } + else + { + return it->second; + } + } + else + return {}; +} + +template +const T parseByMapN(const std::map & map, const JsonNode * val, const std::string & err) +{ + if(val->isNumber()) + return static_cast(val->Integer()); + else + return parseByMap(map, val, err); +} + +void JsonUtils::resolveAddInfo(CAddInfo & var, const JsonNode & node) +{ + const JsonNode & value = node["addInfo"]; + if (!value.isNull()) + { + switch (value.getType()) + { + case JsonNode::JsonType::DATA_INTEGER: + var = static_cast(value.Integer()); + break; + case JsonNode::JsonType::DATA_FLOAT: + var = static_cast(value.Float()); + break; + case JsonNode::JsonType::DATA_STRING: + VLC->identifiers()->requestIdentifier(value, [&](si32 identifier) + { + var = identifier; + }); + break; + case JsonNode::JsonType::DATA_VECTOR: + { + const JsonVector & vec = value.Vector(); + var.resize(vec.size()); + for(int i = 0; i < vec.size(); i++) + { + switch(vec[i].getType()) + { + case JsonNode::JsonType::DATA_INTEGER: + var[i] = static_cast(vec[i].Integer()); + break; + case JsonNode::JsonType::DATA_FLOAT: + var[i] = static_cast(vec[i].Float()); + break; + case JsonNode::JsonType::DATA_STRING: + VLC->identifiers()->requestIdentifier(vec[i], [&var,i](si32 identifier) + { + var[i] = identifier; + }); + break; + default: + logMod->error("Error! Wrong identifier used for value of addInfo[%d].", i); + } + } + break; + } + default: + logMod->error("Error! Wrong identifier used for value of addInfo."); + } + } +} + +std::shared_ptr JsonUtils::parseLimiter(const JsonNode & limiter) +{ + switch(limiter.getType()) + { + case JsonNode::JsonType::DATA_VECTOR: + { + const JsonVector & subLimiters = limiter.Vector(); + if(subLimiters.empty()) + { + logMod->warn("Warning: empty limiter list"); + return std::make_shared(); + } + std::shared_ptr result; + int offset = 1; + // determine limiter type and offset for sub-limiters + if(subLimiters[0].getType() == JsonNode::JsonType::DATA_STRING) + { + const std::string & aggregator = subLimiters[0].String(); + if(aggregator == AllOfLimiter::aggregator) + result = std::make_shared(); + else if(aggregator == AnyOfLimiter::aggregator) + result = std::make_shared(); + else if(aggregator == NoneOfLimiter::aggregator) + result = std::make_shared(); + } + if(!result) + { + // collapse for single limiter without explicit aggregate operator + if(subLimiters.size() == 1) + return parseLimiter(subLimiters[0]); + // implicit aggregator must be allOf + result = std::make_shared(); + offset = 0; + } + if(subLimiters.size() == offset) + logMod->warn("Warning: empty sub-limiter list"); + for(int sl = offset; sl < subLimiters.size(); ++sl) + result->add(parseLimiter(subLimiters[sl])); + return result; + } + break; + case JsonNode::JsonType::DATA_STRING: //pre-defined limiters + return parseByMap(bonusLimiterMap, &limiter, "limiter type "); + break; + case JsonNode::JsonType::DATA_STRUCT: //customizable limiters + { + std::string limiterType = limiter["type"].String(); + const JsonVector & parameters = limiter["parameters"].Vector(); + if(limiterType == "CREATURE_TYPE_LIMITER") + { + auto creatureLimiter = std::make_shared(); + VLC->identifiers()->requestIdentifier("creature", parameters[0], [=](si32 creature) + { + creatureLimiter->setCreature(CreatureID(creature)); + }); + auto includeUpgrades = false; + + if(parameters.size() > 1) + { + bool success = true; + includeUpgrades = parameters[1].TryBoolFromString(success); + + if(!success) + logMod->error("Second parameter of '%s' limiter should be Bool", limiterType); + } + creatureLimiter->includeUpgrades = includeUpgrades; + return creatureLimiter; + } + else if(limiterType == "HAS_ANOTHER_BONUS_LIMITER") + { + std::string anotherBonusType = parameters[0].String(); + auto it = bonusNameMap.find(anotherBonusType); + if(it == bonusNameMap.end()) + { + logMod->error("Error: invalid ability type %s.", anotherBonusType); + } + else + { + auto bonusLimiter = std::make_shared(); + bonusLimiter->type = it->second; + auto findSource = [&](const JsonNode & parameter) + { + if(parameter.getType() == JsonNode::JsonType::DATA_STRUCT) + { + auto sourceIt = bonusSourceMap.find(parameter["type"].String()); + if(sourceIt != bonusSourceMap.end()) + { + bonusLimiter->source = sourceIt->second; + bonusLimiter->isSourceRelevant = true; + if(!parameter["id"].isNull()) { + loadBonusSourceInstance(bonusLimiter->sid, bonusLimiter->source, parameter["id"]); + bonusLimiter->isSourceIDRelevant = true; + } + } + } + return false; + }; + if(parameters.size() > 1) + { + if(findSource(parameters[1]) && parameters.size() == 2) + return bonusLimiter; + else + { + loadBonusSubtype(bonusLimiter->subtype, bonusLimiter->type, parameters[1]); + bonusLimiter->isSubtypeRelevant = true; + if(parameters.size() > 2) + findSource(parameters[2]); + } + } + return bonusLimiter; + } + } + else if(limiterType == "CREATURE_ALIGNMENT_LIMITER") + { + int alignment = vstd::find_pos(GameConstants::ALIGNMENT_NAMES, parameters[0].String()); + if(alignment == -1) + logMod->error("Error: invalid alignment %s.", parameters[0].String()); + else + return std::make_shared(static_cast(alignment)); + } + else if(limiterType == "FACTION_LIMITER" || limiterType == "CREATURE_FACTION_LIMITER") //Second name is deprecated, 1.2 compat + { + auto factionLimiter = std::make_shared(); + VLC->identifiers()->requestIdentifier("faction", parameters[0], [=](si32 faction) + { + factionLimiter->faction = FactionID(faction); + }); + return factionLimiter; + } + else if(limiterType == "CREATURE_LEVEL_LIMITER") + { + auto levelLimiter = std::make_shared(); + if(!parameters.empty()) //If parameters is empty, level limiter works as CREATURES_ONLY limiter + { + levelLimiter->minLevel = parameters[0].Integer(); + if(parameters[1].isNumber()) + levelLimiter->maxLevel = parameters[1].Integer(); + } + return levelLimiter; + } + else if(limiterType == "CREATURE_TERRAIN_LIMITER") + { + auto terrainLimiter = std::make_shared(); + if(!parameters.empty()) + { + VLC->identifiers()->requestIdentifier("terrain", parameters[0], [=](si32 terrain) + { + //TODO: support limiters + //terrainLimiter->terrainType = terrain; + }); + } + return terrainLimiter; + } + else if(limiterType == "UNIT_ON_HEXES") { + auto hexLimiter = std::make_shared(); + if(!parameters.empty()) + { + for (const auto & parameter: parameters){ + if(parameter.isNumber()) + hexLimiter->applicableHexes.insert(BattleHex(parameter.Integer())); + } + } + return hexLimiter; + } + else + { + logMod->error("Error: invalid customizable limiter type %s.", limiterType); + } + } + break; + default: + break; + } + return nullptr; +} + +std::shared_ptr JsonUtils::parseBonus(const JsonNode &ability) +{ + auto b = std::make_shared(); + if (!parseBonus(ability, b.get())) + { + // caller code can not handle this case and presumes that returned bonus is always valid + logGlobal->error("Failed to parse bonus! Json config was %S ", ability.toJson()); + b->type = BonusType::NONE; + return b; + } + return b; +} + +std::shared_ptr JsonUtils::parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description) +{ + /* duration = BonusDuration::PERMANENT + source = BonusSource::TOWN_STRUCTURE + bonusType, val, subtype - get from json + */ + auto b = std::make_shared(BonusDuration::PERMANENT, BonusType::NONE, BonusSource::TOWN_STRUCTURE, 0, BuildingTypeUniqueID(faction, building), description); + + if(!parseBonus(ability, b.get())) + return nullptr; + return b; +} + +static BonusParams convertDeprecatedBonus(const JsonNode &ability) +{ + if(vstd::contains(deprecatedBonusSet, ability["type"].String())) + { + logMod->warn("There is deprecated bonus found:\n%s\nTrying to convert...", ability.toJson()); + auto params = BonusParams(ability["type"].String(), + ability["subtype"].isString() ? ability["subtype"].String() : "", + ability["subtype"].isNumber() ? ability["subtype"].Integer() : -1); + if(params.isConverted) + { + if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && bonusValueMap.find(ability["valueType"].String())->second == BonusValueType::PERCENT_TO_BASE) //assume secondary skill special + { + params.valueType = BonusValueType::PERCENT_TO_TARGET_TYPE; + params.targetType = BonusSource::SECONDARY_SKILL; + } + + logMod->warn("Please, use this bonus:\n%s\nConverted successfully!", params.toJson().toJson()); + return params; + } + else + logMod->error("Cannot convert bonus!\n%s", ability.toJson()); + } + BonusParams ret; + ret.isConverted = false; + return ret; +} + +static TUpdaterPtr parseUpdater(const JsonNode & updaterJson) +{ + switch(updaterJson.getType()) + { + case JsonNode::JsonType::DATA_STRING: + return parseByMap(bonusUpdaterMap, &updaterJson, "updater type "); + break; + case JsonNode::JsonType::DATA_STRUCT: + if(updaterJson["type"].String() == "GROWS_WITH_LEVEL") + { + auto updater = std::make_shared(); + const JsonVector param = updaterJson["parameters"].Vector(); + updater->valPer20 = static_cast(param[0].Integer()); + if(param.size() > 1) + updater->stepSize = static_cast(param[1].Integer()); + return updater; + } + else if (updaterJson["type"].String() == "ARMY_MOVEMENT") + { + auto updater = std::make_shared(); + if(updaterJson["parameters"].isVector()) + { + const auto & param = updaterJson["parameters"].Vector(); + if(param.size() < 4) + logMod->warn("Invalid ARMY_MOVEMENT parameters, using default!"); + else + { + updater->base = static_cast(param.at(0).Integer()); + updater->divider = static_cast(param.at(1).Integer()); + updater->multiplier = static_cast(param.at(2).Integer()); + updater->max = static_cast(param.at(3).Integer()); + } + return updater; + } + } + else + logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String()); + break; + } + return nullptr; +} + +bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b) +{ + const JsonNode * value = nullptr; + + std::string type = ability["type"].String(); + auto it = bonusNameMap.find(type); + auto params = std::make_unique(false); + if (it == bonusNameMap.end()) + { + params = std::make_unique(convertDeprecatedBonus(ability)); + if(!params->isConverted) + { + logMod->error("Error: invalid ability type %s.", type); + return false; + } + b->type = params->type; + b->val = params->val.value_or(0); + b->valType = params->valueType.value_or(BonusValueType::ADDITIVE_VALUE); + if(params->targetType) + b->targetSourceType = params->targetType.value(); + } + else + b->type = it->second; + + loadBonusSubtype(b->subtype, b->type, params->isConverted ? params->toJson()["subtype"] : ability["subtype"]); + + if(!params->isConverted) + { + b->val = static_cast(ability["val"].Float()); + + value = &ability["valueType"]; + if (!value->isNull()) + b->valType = static_cast(parseByMapN(bonusValueMap, value, "value type ")); + } + + b->stacking = ability["stacking"].String(); + + resolveAddInfo(b->additionalInfo, ability); + + b->turnsRemain = static_cast(ability["turns"].Float()); + + if(!ability["description"].isNull()) + { + if (ability["description"].isString()) + b->description = ability["description"].String(); + if (ability["description"].isNumber()) + b->description = VLC->generaltexth->translate("core.arraytxt", ability["description"].Integer()); + } + + value = &ability["effectRange"]; + if (!value->isNull()) + b->effectRange = static_cast(parseByMapN(bonusLimitEffect, value, "effect range ")); + + value = &ability["duration"]; + if (!value->isNull()) + { + switch (value->getType()) + { + case JsonNode::JsonType::DATA_STRING: + b->duration = parseByMap(bonusDurationMap, value, "duration type "); + break; + case JsonNode::JsonType::DATA_VECTOR: + { + BonusDuration::Type dur = 0; + for (const JsonNode & d : value->Vector()) + dur |= parseByMapN(bonusDurationMap, &d, "duration type "); + b->duration = dur; + } + break; + default: + logMod->error("Error! Wrong bonus duration format."); + } + } + + value = &ability["sourceType"]; + if (!value->isNull()) + b->source = static_cast(parseByMap(bonusSourceMap, value, "source type ")); + + if (!ability["sourceID"].isNull()) + loadBonusSourceInstance(b->sid, b->source, ability["sourceID"]); + + value = &ability["targetSourceType"]; + if (!value->isNull()) + b->targetSourceType = static_cast(parseByMap(bonusSourceMap, value, "target type ")); + + value = &ability["limiters"]; + if (!value->isNull()) + b->limiter = parseLimiter(*value); + + value = &ability["propagator"]; + if (!value->isNull()) + { + //ALL_CREATURES old propagator compatibility + if(value->String() == "ALL_CREATURES") + { + logMod->warn("ALL_CREATURES propagator is deprecated. Use GLOBAL_EFFECT propagator with CREATURES_ONLY limiter"); + b->addLimiter(std::make_shared()); + b->propagator = bonusPropagatorMap.at("GLOBAL_EFFECT"); + } + else + b->propagator = parseByMap(bonusPropagatorMap, value, "propagator type "); + } + + value = &ability["updater"]; + if(!value->isNull()) + b->addUpdater(parseUpdater(*value)); + value = &ability["propagationUpdater"]; + if(!value->isNull()) + b->propagationUpdater = parseUpdater(*value); + return true; +} + +CSelector JsonUtils::parseSelector(const JsonNode & ability) +{ + CSelector ret = Selector::all; + + // Recursive parsers for anyOf, allOf, noneOf + const auto * value = &ability["allOf"]; + if(value->isVector()) + { + for(const auto & andN : value->Vector()) + ret = ret.And(parseSelector(andN)); + } + + value = &ability["anyOf"]; + if(value->isVector()) + { + CSelector base = Selector::none; + for(const auto & andN : value->Vector()) + base = base.Or(parseSelector(andN)); + + ret = ret.And(base); + } + + value = &ability["noneOf"]; + if(value->isVector()) + { + CSelector base = Selector::none; + for(const auto & andN : value->Vector()) + base = base.Or(parseSelector(andN)); + + ret = ret.And(base.Not()); + } + + BonusType type = BonusType::NONE; + + // Actual selector parser + value = &ability["type"]; + if(value->isString()) + { + auto it = bonusNameMap.find(value->String()); + if(it != bonusNameMap.end()) + { + type = it->second; + ret = ret.And(Selector::type()(it->second)); + } + } + value = &ability["subtype"]; + if(!value->isNull() && type != BonusType::NONE) + { + BonusSubtypeID subtype; + loadBonusSubtype(subtype, type, ability); + ret = ret.And(Selector::subtype()(subtype)); + } + value = &ability["sourceType"]; + std::optional src = std::nullopt; //Fixes for GCC false maybe-uninitialized + std::optional id = std::nullopt; + if(value->isString()) + { + auto it = bonusSourceMap.find(value->String()); + if(it != bonusSourceMap.end()) + src = it->second; + } + + value = &ability["sourceID"]; + if(!value->isNull() && src.has_value()) + { + loadBonusSourceInstance(*id, *src, ability); + } + + if(src && id) + ret = ret.And(Selector::source(*src, *id)); + else if(src) + ret = ret.And(Selector::sourceTypeSel(*src)); + + + value = &ability["targetSourceType"]; + if(value->isString()) + { + auto it = bonusSourceMap.find(value->String()); + if(it != bonusSourceMap.end()) + ret = ret.And(Selector::targetSourceType()(it->second)); + } + value = &ability["valueType"]; + if(value->isString()) + { + auto it = bonusValueMap.find(value->String()); + if(it != bonusValueMap.end()) + ret = ret.And(Selector::valueType(it->second)); + } + CAddInfo info; + value = &ability["addInfo"]; + if(!value->isNull()) + { + resolveAddInfo(info, ability["addInfo"]); + ret = ret.And(Selector::info()(info)); + } + value = &ability["effectRange"]; + if(value->isString()) + { + auto it = bonusLimitEffect.find(value->String()); + if(it != bonusLimitEffect.end()) + ret = ret.And(Selector::effectRange()(it->second)); + } + value = &ability["lastsTurns"]; + if(value->isNumber()) + ret = ret.And(Selector::turns(value->Integer())); + value = &ability["lastsDays"]; + if(value->isNumber()) + ret = ret.And(Selector::days(value->Integer())); + + return ret; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonBonus.h b/lib/json/JsonBonus.h new file mode 100644 index 000000000..c994d63e5 --- /dev/null +++ b/lib/json/JsonBonus.h @@ -0,0 +1,28 @@ +/* + * JsonBonus.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 "JsonNode.h" +#include "../GameConstants.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace JsonUtils +{ + DLL_LINKAGE std::shared_ptr parseBonus(const JsonVector & ability_vec); + DLL_LINKAGE std::shared_ptr parseBonus(const JsonNode & ability); + DLL_LINKAGE std::shared_ptr parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description); + DLL_LINKAGE bool parseBonus(const JsonNode & ability, Bonus * placement); + DLL_LINKAGE std::shared_ptr parseLimiter(const JsonNode & limiter); + DLL_LINKAGE CSelector parseSelector(const JsonNode &ability); + DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonRandom.cpp b/lib/json/JsonRandom.cpp index e9527f4a2..9490ce244 100644 --- a/lib/json/JsonRandom.cpp +++ b/lib/json/JsonRandom.cpp @@ -13,7 +13,7 @@ #include -#include "JsonUtils.h" +#include "JsonBonus.h" #include "../CRandomGenerator.h" #include "../constants/StringConstants.h" diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 8d1cb7152..7597c37b8 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -30,842 +30,6 @@ VCMI_LIB_NAMESPACE_BEGIN static const JsonNode nullNode; -static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node) -{ - if (node.isNull()) - { - subtype = BonusSubtypeID(); - return; - } - - if (node.isNumber()) // Compatibility code for 1.3 or older - { - logMod->warn("Bonus subtype must be string! (%s)", node.meta); - subtype = BonusCustomSubtype(node.Integer()); - return; - } - - if (!node.isString()) - { - logMod->warn("Bonus subtype must be string! (%s)", node.meta); - subtype = BonusSubtypeID(); - return; - } - - switch (type) - { - case BonusType::MAGIC_SCHOOL_SKILL: - case BonusType::SPELL_DAMAGE: - case BonusType::SPELLS_OF_SCHOOL: - case BonusType::SPELL_DAMAGE_REDUCTION: - case BonusType::SPELL_SCHOOL_IMMUNITY: - case BonusType::NEGATIVE_EFFECTS_IMMUNITY: - { - VLC->identifiers()->requestIdentifier( "spellSchool", node, [&subtype](int32_t identifier) - { - subtype = SpellSchool(identifier); - }); - break; - } - case BonusType::NO_TERRAIN_PENALTY: - { - VLC->identifiers()->requestIdentifier( "terrain", node, [&subtype](int32_t identifier) - { - subtype = TerrainId(identifier); - }); - break; - } - case BonusType::PRIMARY_SKILL: - { - VLC->identifiers()->requestIdentifier( "primarySkill", node, [&subtype](int32_t identifier) - { - subtype = PrimarySkill(identifier); - }); - break; - } - case BonusType::IMPROVED_NECROMANCY: - case BonusType::HERO_GRANTS_ATTACKS: - case BonusType::BONUS_DAMAGE_CHANCE: - case BonusType::BONUS_DAMAGE_PERCENTAGE: - case BonusType::SPECIAL_UPGRADE: - case BonusType::HATE: - case BonusType::SUMMON_GUARDIANS: - case BonusType::MANUAL_CONTROL: - { - VLC->identifiers()->requestIdentifier( "creature", node, [&subtype](int32_t identifier) - { - subtype = CreatureID(identifier); - }); - break; - } - case BonusType::SPELL_IMMUNITY: - case BonusType::SPELL_DURATION: - case BonusType::SPECIAL_ADD_VALUE_ENCHANT: - case BonusType::SPECIAL_FIXED_VALUE_ENCHANT: - case BonusType::SPECIAL_PECULIAR_ENCHANT: - case BonusType::SPECIAL_SPELL_LEV: - case BonusType::SPECIFIC_SPELL_DAMAGE: - case BonusType::SPELL: - case BonusType::OPENING_BATTLE_SPELL: - case BonusType::SPELL_LIKE_ATTACK: - case BonusType::CATAPULT: - case BonusType::CATAPULT_EXTRA_SHOTS: - case BonusType::HEALER: - case BonusType::SPELLCASTER: - case BonusType::ENCHANTER: - case BonusType::SPELL_AFTER_ATTACK: - case BonusType::SPELL_BEFORE_ATTACK: - case BonusType::SPECIFIC_SPELL_POWER: - case BonusType::ENCHANTED: - case BonusType::MORE_DAMAGE_FROM_SPELL: - case BonusType::NOT_ACTIVE: - { - VLC->identifiers()->requestIdentifier( "spell", node, [&subtype](int32_t identifier) - { - subtype = SpellID(identifier); - }); - break; - } - case BonusType::GENERATE_RESOURCE: - { - VLC->identifiers()->requestIdentifier( "resource", node, [&subtype](int32_t identifier) - { - subtype = GameResID(identifier); - }); - break; - } - case BonusType::MOVEMENT: - case BonusType::WATER_WALKING: - case BonusType::FLYING_MOVEMENT: - case BonusType::NEGATE_ALL_NATURAL_IMMUNITIES: - case BonusType::CREATURE_DAMAGE: - case BonusType::FLYING: - case BonusType::FIRST_STRIKE: - case BonusType::GENERAL_DAMAGE_REDUCTION: - case BonusType::PERCENTAGE_DAMAGE_BOOST: - case BonusType::SOUL_STEAL: - case BonusType::TRANSMUTATION: - case BonusType::DESTRUCTION: - case BonusType::DEATH_STARE: - case BonusType::REBIRTH: - case BonusType::VISIONS: - case BonusType::SPELLS_OF_LEVEL: // spell level - case BonusType::CREATURE_GROWTH: // creature level - { - VLC->identifiers()->requestIdentifier( "bonusSubtype", node, [&subtype](int32_t identifier) - { - subtype = BonusCustomSubtype(identifier); - }); - break; - } - default: - for(const auto & i : bonusNameMap) - if(i.second == type) - logMod->warn("Bonus type %s does not supports subtypes!", i.first ); - subtype = BonusSubtypeID(); - } -} - -static void loadBonusSourceInstance(BonusSourceID & sourceInstance, BonusSource sourceType, const JsonNode & node) -{ - if (node.isNull()) - { - sourceInstance = BonusCustomSource(); - return; - } - - if (node.isNumber()) // Compatibility code for 1.3 or older - { - logMod->warn("Bonus source must be string!"); - sourceInstance = BonusCustomSource(node.Integer()); - return; - } - - if (!node.isString()) - { - logMod->warn("Bonus source must be string!"); - sourceInstance = BonusCustomSource(); - return; - } - - switch (sourceType) - { - case BonusSource::ARTIFACT: - case BonusSource::ARTIFACT_INSTANCE: - { - VLC->identifiers()->requestIdentifier( "artifact", node, [&sourceInstance](int32_t identifier) - { - sourceInstance = ArtifactID(identifier); - }); - break; - } - case BonusSource::OBJECT_TYPE: - { - VLC->identifiers()->requestIdentifier( "object", node, [&sourceInstance](int32_t identifier) - { - sourceInstance = Obj(identifier); - }); - break; - } - case BonusSource::OBJECT_INSTANCE: - case BonusSource::HERO_BASE_SKILL: - sourceInstance = ObjectInstanceID(ObjectInstanceID::decode(node.String())); - break; - case BonusSource::CREATURE_ABILITY: - { - VLC->identifiers()->requestIdentifier( "creature", node, [&sourceInstance](int32_t identifier) - { - sourceInstance = CreatureID(identifier); - }); - break; - } - case BonusSource::TERRAIN_OVERLAY: - { - VLC->identifiers()->requestIdentifier( "spell", node, [&sourceInstance](int32_t identifier) - { - sourceInstance = BattleField(identifier); - }); - break; - } - case BonusSource::SPELL_EFFECT: - { - VLC->identifiers()->requestIdentifier( "spell", node, [&sourceInstance](int32_t identifier) - { - sourceInstance = SpellID(identifier); - }); - break; - } - case BonusSource::TOWN_STRUCTURE: - assert(0); // TODO - sourceInstance = BuildingTypeUniqueID(); - break; - case BonusSource::SECONDARY_SKILL: - { - VLC->identifiers()->requestIdentifier( "secondarySkill", node, [&sourceInstance](int32_t identifier) - { - sourceInstance = SecondarySkill(identifier); - }); - break; - } - case BonusSource::HERO_SPECIAL: - { - VLC->identifiers()->requestIdentifier( "hero", node, [&sourceInstance](int32_t identifier) - { - sourceInstance = HeroTypeID(identifier); - }); - break; - } - case BonusSource::CAMPAIGN_BONUS: - sourceInstance = CampaignScenarioID(CampaignScenarioID::decode(node.String())); - break; - case BonusSource::ARMY: - case BonusSource::STACK_EXPERIENCE: - case BonusSource::COMMANDER: - case BonusSource::GLOBAL: - case BonusSource::TERRAIN_NATIVE: - case BonusSource::OTHER: - default: - sourceInstance = BonusSourceID(); - break; - } -} - -std::shared_ptr JsonUtils::parseBonus(const JsonVector & ability_vec) -{ - auto b = std::make_shared(); - std::string type = ability_vec[0].String(); - auto it = bonusNameMap.find(type); - if (it == bonusNameMap.end()) - { - logMod->error("Error: invalid ability type %s.", type); - return b; - } - b->type = it->second; - - b->val = static_cast(ability_vec[1].Float()); - loadBonusSubtype(b->subtype, b->type, ability_vec[2]); - b->additionalInfo = static_cast(ability_vec[3].Float()); - b->duration = BonusDuration::PERMANENT; //TODO: handle flags (as integer) - b->turnsRemain = 0; - return b; -} - -template -const T parseByMap(const std::map & map, const JsonNode * val, const std::string & err) -{ - if (!val->isNull()) - { - const std::string & type = val->String(); - auto it = map.find(type); - if (it == map.end()) - { - logMod->error("Error: invalid %s%s.", err, type); - return {}; - } - else - { - return it->second; - } - } - else - return {}; -} - -template -const T parseByMapN(const std::map & map, const JsonNode * val, const std::string & err) -{ - if(val->isNumber()) - return static_cast(val->Integer()); - else - return parseByMap(map, val, err); -} - -void JsonUtils::resolveAddInfo(CAddInfo & var, const JsonNode & node) -{ - const JsonNode & value = node["addInfo"]; - if (!value.isNull()) - { - switch (value.getType()) - { - case JsonNode::JsonType::DATA_INTEGER: - var = static_cast(value.Integer()); - break; - case JsonNode::JsonType::DATA_FLOAT: - var = static_cast(value.Float()); - break; - case JsonNode::JsonType::DATA_STRING: - VLC->identifiers()->requestIdentifier(value, [&](si32 identifier) - { - var = identifier; - }); - break; - case JsonNode::JsonType::DATA_VECTOR: - { - const JsonVector & vec = value.Vector(); - var.resize(vec.size()); - for(int i = 0; i < vec.size(); i++) - { - switch(vec[i].getType()) - { - case JsonNode::JsonType::DATA_INTEGER: - var[i] = static_cast(vec[i].Integer()); - break; - case JsonNode::JsonType::DATA_FLOAT: - var[i] = static_cast(vec[i].Float()); - break; - case JsonNode::JsonType::DATA_STRING: - VLC->identifiers()->requestIdentifier(vec[i], [&var,i](si32 identifier) - { - var[i] = identifier; - }); - break; - default: - logMod->error("Error! Wrong identifier used for value of addInfo[%d].", i); - } - } - break; - } - default: - logMod->error("Error! Wrong identifier used for value of addInfo."); - } - } -} - -std::shared_ptr JsonUtils::parseLimiter(const JsonNode & limiter) -{ - switch(limiter.getType()) - { - case JsonNode::JsonType::DATA_VECTOR: - { - const JsonVector & subLimiters = limiter.Vector(); - if(subLimiters.empty()) - { - logMod->warn("Warning: empty limiter list"); - return std::make_shared(); - } - std::shared_ptr result; - int offset = 1; - // determine limiter type and offset for sub-limiters - if(subLimiters[0].getType() == JsonNode::JsonType::DATA_STRING) - { - const std::string & aggregator = subLimiters[0].String(); - if(aggregator == AllOfLimiter::aggregator) - result = std::make_shared(); - else if(aggregator == AnyOfLimiter::aggregator) - result = std::make_shared(); - else if(aggregator == NoneOfLimiter::aggregator) - result = std::make_shared(); - } - if(!result) - { - // collapse for single limiter without explicit aggregate operator - if(subLimiters.size() == 1) - return parseLimiter(subLimiters[0]); - // implicit aggregator must be allOf - result = std::make_shared(); - offset = 0; - } - if(subLimiters.size() == offset) - logMod->warn("Warning: empty sub-limiter list"); - for(int sl = offset; sl < subLimiters.size(); ++sl) - result->add(parseLimiter(subLimiters[sl])); - return result; - } - break; - case JsonNode::JsonType::DATA_STRING: //pre-defined limiters - return parseByMap(bonusLimiterMap, &limiter, "limiter type "); - break; - case JsonNode::JsonType::DATA_STRUCT: //customizable limiters - { - std::string limiterType = limiter["type"].String(); - const JsonVector & parameters = limiter["parameters"].Vector(); - if(limiterType == "CREATURE_TYPE_LIMITER") - { - auto creatureLimiter = std::make_shared(); - VLC->identifiers()->requestIdentifier("creature", parameters[0], [=](si32 creature) - { - creatureLimiter->setCreature(CreatureID(creature)); - }); - auto includeUpgrades = false; - - if(parameters.size() > 1) - { - bool success = true; - includeUpgrades = parameters[1].TryBoolFromString(success); - - if(!success) - logMod->error("Second parameter of '%s' limiter should be Bool", limiterType); - } - creatureLimiter->includeUpgrades = includeUpgrades; - return creatureLimiter; - } - else if(limiterType == "HAS_ANOTHER_BONUS_LIMITER") - { - std::string anotherBonusType = parameters[0].String(); - auto it = bonusNameMap.find(anotherBonusType); - if(it == bonusNameMap.end()) - { - logMod->error("Error: invalid ability type %s.", anotherBonusType); - } - else - { - auto bonusLimiter = std::make_shared(); - bonusLimiter->type = it->second; - auto findSource = [&](const JsonNode & parameter) - { - if(parameter.getType() == JsonNode::JsonType::DATA_STRUCT) - { - auto sourceIt = bonusSourceMap.find(parameter["type"].String()); - if(sourceIt != bonusSourceMap.end()) - { - bonusLimiter->source = sourceIt->second; - bonusLimiter->isSourceRelevant = true; - if(!parameter["id"].isNull()) { - loadBonusSourceInstance(bonusLimiter->sid, bonusLimiter->source, parameter["id"]); - bonusLimiter->isSourceIDRelevant = true; - } - } - } - return false; - }; - if(parameters.size() > 1) - { - if(findSource(parameters[1]) && parameters.size() == 2) - return bonusLimiter; - else - { - loadBonusSubtype(bonusLimiter->subtype, bonusLimiter->type, parameters[1]); - bonusLimiter->isSubtypeRelevant = true; - if(parameters.size() > 2) - findSource(parameters[2]); - } - } - return bonusLimiter; - } - } - else if(limiterType == "CREATURE_ALIGNMENT_LIMITER") - { - int alignment = vstd::find_pos(GameConstants::ALIGNMENT_NAMES, parameters[0].String()); - if(alignment == -1) - logMod->error("Error: invalid alignment %s.", parameters[0].String()); - else - return std::make_shared(static_cast(alignment)); - } - else if(limiterType == "FACTION_LIMITER" || limiterType == "CREATURE_FACTION_LIMITER") //Second name is deprecated, 1.2 compat - { - auto factionLimiter = std::make_shared(); - VLC->identifiers()->requestIdentifier("faction", parameters[0], [=](si32 faction) - { - factionLimiter->faction = FactionID(faction); - }); - return factionLimiter; - } - else if(limiterType == "CREATURE_LEVEL_LIMITER") - { - auto levelLimiter = std::make_shared(); - if(!parameters.empty()) //If parameters is empty, level limiter works as CREATURES_ONLY limiter - { - levelLimiter->minLevel = parameters[0].Integer(); - if(parameters[1].isNumber()) - levelLimiter->maxLevel = parameters[1].Integer(); - } - return levelLimiter; - } - else if(limiterType == "CREATURE_TERRAIN_LIMITER") - { - auto terrainLimiter = std::make_shared(); - if(!parameters.empty()) - { - VLC->identifiers()->requestIdentifier("terrain", parameters[0], [=](si32 terrain) - { - //TODO: support limiters - //terrainLimiter->terrainType = terrain; - }); - } - return terrainLimiter; - } - else if(limiterType == "UNIT_ON_HEXES") { - auto hexLimiter = std::make_shared(); - if(!parameters.empty()) - { - for (const auto & parameter: parameters){ - if(parameter.isNumber()) - hexLimiter->applicableHexes.insert(BattleHex(parameter.Integer())); - } - } - return hexLimiter; - } - else - { - logMod->error("Error: invalid customizable limiter type %s.", limiterType); - } - } - break; - default: - break; - } - return nullptr; -} - -std::shared_ptr JsonUtils::parseBonus(const JsonNode &ability) -{ - auto b = std::make_shared(); - if (!parseBonus(ability, b.get())) - { - // caller code can not handle this case and presumes that returned bonus is always valid - logGlobal->error("Failed to parse bonus! Json config was %S ", ability.toJson()); - b->type = BonusType::NONE; - return b; - } - return b; -} - -std::shared_ptr JsonUtils::parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description) -{ - /* duration = BonusDuration::PERMANENT - source = BonusSource::TOWN_STRUCTURE - bonusType, val, subtype - get from json - */ - auto b = std::make_shared(BonusDuration::PERMANENT, BonusType::NONE, BonusSource::TOWN_STRUCTURE, 0, BuildingTypeUniqueID(faction, building), description); - - if(!parseBonus(ability, b.get())) - return nullptr; - return b; -} - -static BonusParams convertDeprecatedBonus(const JsonNode &ability) -{ - if(vstd::contains(deprecatedBonusSet, ability["type"].String())) - { - logMod->warn("There is deprecated bonus found:\n%s\nTrying to convert...", ability.toJson()); - auto params = BonusParams(ability["type"].String(), - ability["subtype"].isString() ? ability["subtype"].String() : "", - ability["subtype"].isNumber() ? ability["subtype"].Integer() : -1); - if(params.isConverted) - { - if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && bonusValueMap.find(ability["valueType"].String())->second == BonusValueType::PERCENT_TO_BASE) //assume secondary skill special - { - params.valueType = BonusValueType::PERCENT_TO_TARGET_TYPE; - params.targetType = BonusSource::SECONDARY_SKILL; - } - - logMod->warn("Please, use this bonus:\n%s\nConverted successfully!", params.toJson().toJson()); - return params; - } - else - logMod->error("Cannot convert bonus!\n%s", ability.toJson()); - } - BonusParams ret; - ret.isConverted = false; - return ret; -} - -static TUpdaterPtr parseUpdater(const JsonNode & updaterJson) -{ - switch(updaterJson.getType()) - { - case JsonNode::JsonType::DATA_STRING: - return parseByMap(bonusUpdaterMap, &updaterJson, "updater type "); - break; - case JsonNode::JsonType::DATA_STRUCT: - if(updaterJson["type"].String() == "GROWS_WITH_LEVEL") - { - auto updater = std::make_shared(); - const JsonVector param = updaterJson["parameters"].Vector(); - updater->valPer20 = static_cast(param[0].Integer()); - if(param.size() > 1) - updater->stepSize = static_cast(param[1].Integer()); - return updater; - } - else if (updaterJson["type"].String() == "ARMY_MOVEMENT") - { - auto updater = std::make_shared(); - if(updaterJson["parameters"].isVector()) - { - const auto & param = updaterJson["parameters"].Vector(); - if(param.size() < 4) - logMod->warn("Invalid ARMY_MOVEMENT parameters, using default!"); - else - { - updater->base = static_cast(param.at(0).Integer()); - updater->divider = static_cast(param.at(1).Integer()); - updater->multiplier = static_cast(param.at(2).Integer()); - updater->max = static_cast(param.at(3).Integer()); - } - return updater; - } - } - else - logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String()); - break; - } - return nullptr; -} - -bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b) -{ - const JsonNode * value = nullptr; - - std::string type = ability["type"].String(); - auto it = bonusNameMap.find(type); - auto params = std::make_unique(false); - if (it == bonusNameMap.end()) - { - params = std::make_unique(convertDeprecatedBonus(ability)); - if(!params->isConverted) - { - logMod->error("Error: invalid ability type %s.", type); - return false; - } - b->type = params->type; - b->val = params->val.value_or(0); - b->valType = params->valueType.value_or(BonusValueType::ADDITIVE_VALUE); - if(params->targetType) - b->targetSourceType = params->targetType.value(); - } - else - b->type = it->second; - - loadBonusSubtype(b->subtype, b->type, params->isConverted ? params->toJson()["subtype"] : ability["subtype"]); - - if(!params->isConverted) - { - b->val = static_cast(ability["val"].Float()); - - value = &ability["valueType"]; - if (!value->isNull()) - b->valType = static_cast(parseByMapN(bonusValueMap, value, "value type ")); - } - - b->stacking = ability["stacking"].String(); - - resolveAddInfo(b->additionalInfo, ability); - - b->turnsRemain = static_cast(ability["turns"].Float()); - - if(!ability["description"].isNull()) - { - if (ability["description"].isString()) - b->description = ability["description"].String(); - if (ability["description"].isNumber()) - b->description = VLC->generaltexth->translate("core.arraytxt", ability["description"].Integer()); - } - - value = &ability["effectRange"]; - if (!value->isNull()) - b->effectRange = static_cast(parseByMapN(bonusLimitEffect, value, "effect range ")); - - value = &ability["duration"]; - if (!value->isNull()) - { - switch (value->getType()) - { - case JsonNode::JsonType::DATA_STRING: - b->duration = parseByMap(bonusDurationMap, value, "duration type "); - break; - case JsonNode::JsonType::DATA_VECTOR: - { - BonusDuration::Type dur = 0; - for (const JsonNode & d : value->Vector()) - dur |= parseByMapN(bonusDurationMap, &d, "duration type "); - b->duration = dur; - } - break; - default: - logMod->error("Error! Wrong bonus duration format."); - } - } - - value = &ability["sourceType"]; - if (!value->isNull()) - b->source = static_cast(parseByMap(bonusSourceMap, value, "source type ")); - - if (!ability["sourceID"].isNull()) - loadBonusSourceInstance(b->sid, b->source, ability["sourceID"]); - - value = &ability["targetSourceType"]; - if (!value->isNull()) - b->targetSourceType = static_cast(parseByMap(bonusSourceMap, value, "target type ")); - - value = &ability["limiters"]; - if (!value->isNull()) - b->limiter = parseLimiter(*value); - - value = &ability["propagator"]; - if (!value->isNull()) - { - //ALL_CREATURES old propagator compatibility - if(value->String() == "ALL_CREATURES") - { - logMod->warn("ALL_CREATURES propagator is deprecated. Use GLOBAL_EFFECT propagator with CREATURES_ONLY limiter"); - b->addLimiter(std::make_shared()); - b->propagator = bonusPropagatorMap.at("GLOBAL_EFFECT"); - } - else - b->propagator = parseByMap(bonusPropagatorMap, value, "propagator type "); - } - - value = &ability["updater"]; - if(!value->isNull()) - b->addUpdater(parseUpdater(*value)); - value = &ability["propagationUpdater"]; - if(!value->isNull()) - b->propagationUpdater = parseUpdater(*value); - return true; -} - -CSelector JsonUtils::parseSelector(const JsonNode & ability) -{ - CSelector ret = Selector::all; - - // Recursive parsers for anyOf, allOf, noneOf - const auto * value = &ability["allOf"]; - if(value->isVector()) - { - for(const auto & andN : value->Vector()) - ret = ret.And(parseSelector(andN)); - } - - value = &ability["anyOf"]; - if(value->isVector()) - { - CSelector base = Selector::none; - for(const auto & andN : value->Vector()) - base = base.Or(parseSelector(andN)); - - ret = ret.And(base); - } - - value = &ability["noneOf"]; - if(value->isVector()) - { - CSelector base = Selector::none; - for(const auto & andN : value->Vector()) - base = base.Or(parseSelector(andN)); - - ret = ret.And(base.Not()); - } - - BonusType type = BonusType::NONE; - - // Actual selector parser - value = &ability["type"]; - if(value->isString()) - { - auto it = bonusNameMap.find(value->String()); - if(it != bonusNameMap.end()) - { - type = it->second; - ret = ret.And(Selector::type()(it->second)); - } - } - value = &ability["subtype"]; - if(!value->isNull() && type != BonusType::NONE) - { - BonusSubtypeID subtype; - loadBonusSubtype(subtype, type, ability); - ret = ret.And(Selector::subtype()(subtype)); - } - value = &ability["sourceType"]; - std::optional src = std::nullopt; //Fixes for GCC false maybe-uninitialized - std::optional id = std::nullopt; - if(value->isString()) - { - auto it = bonusSourceMap.find(value->String()); - if(it != bonusSourceMap.end()) - src = it->second; - } - - value = &ability["sourceID"]; - if(!value->isNull() && src.has_value()) - { - loadBonusSourceInstance(*id, *src, ability); - } - - if(src && id) - ret = ret.And(Selector::source(*src, *id)); - else if(src) - ret = ret.And(Selector::sourceTypeSel(*src)); - - - value = &ability["targetSourceType"]; - if(value->isString()) - { - auto it = bonusSourceMap.find(value->String()); - if(it != bonusSourceMap.end()) - ret = ret.And(Selector::targetSourceType()(it->second)); - } - value = &ability["valueType"]; - if(value->isString()) - { - auto it = bonusValueMap.find(value->String()); - if(it != bonusValueMap.end()) - ret = ret.And(Selector::valueType(it->second)); - } - CAddInfo info; - value = &ability["addInfo"]; - if(!value->isNull()) - { - resolveAddInfo(info, ability["addInfo"]); - ret = ret.And(Selector::info()(info)); - } - value = &ability["effectRange"]; - if(value->isString()) - { - auto it = bonusLimitEffect.find(value->String()); - if(it != bonusLimitEffect.end()) - ret = ret.And(Selector::effectRange()(it->second)); - } - value = &ability["lastsTurns"]; - if(value->isNumber()) - ret = ret.And(Selector::turns(value->Integer())); - value = &ability["lastsDays"]; - if(value->isNumber()) - ret = ret.And(Selector::days(value->Integer())); - - return ret; -} - //returns first Key with value equal to given one template Key reverseMapFirst(const Val & val, const std::map & map) diff --git a/lib/json/JsonUtils.h b/lib/json/JsonUtils.h index ce1d88141..ec590e1d1 100644 --- a/lib/json/JsonUtils.h +++ b/lib/json/JsonUtils.h @@ -10,20 +10,11 @@ #pragma once #include "JsonNode.h" -#include "../GameConstants.h" VCMI_LIB_NAMESPACE_BEGIN namespace JsonUtils { - DLL_LINKAGE std::shared_ptr parseBonus(const JsonVector & ability_vec); - DLL_LINKAGE std::shared_ptr parseBonus(const JsonNode & ability); - DLL_LINKAGE std::shared_ptr parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description); - DLL_LINKAGE bool parseBonus(const JsonNode & ability, Bonus * placement); - DLL_LINKAGE std::shared_ptr parseLimiter(const JsonNode & limiter); - DLL_LINKAGE CSelector parseSelector(const JsonNode &ability); - DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node); - /** * @brief recursively merges source into dest, replacing identical fields * struct : recursively calls this function diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 0b4dca5b6..be24c0258 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -32,7 +32,7 @@ #include "../StartInfo.h" #include "CGTownInstance.h" #include "../campaign/CampaignState.h" -#include "../json/JsonUtils.h" +#include "../json/JsonBonus.h" #include "../pathfinder/TurnInfo.h" #include "../serializer/JsonSerializeFormat.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" diff --git a/lib/serializer/JsonUpdater.cpp b/lib/serializer/JsonUpdater.cpp index d1719a029..92b4bfc45 100644 --- a/lib/serializer/JsonUpdater.cpp +++ b/lib/serializer/JsonUpdater.cpp @@ -12,7 +12,7 @@ #include "../bonuses/CBonusSystemNode.h" #include "../bonuses/Bonus.h" -#include "../json/JsonUtils.h" +#include "../json/JsonBonus.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index e5908340a..0aa239f93 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -25,6 +25,7 @@ #include "../battle/BattleInfo.h" #include "../battle/CBattleInfoCallback.h" #include "../battle/Unit.h" +#include "../json/JsonBonus.h" #include "../json/JsonUtils.h" #include "../mapObjects/CGHeroInstance.h" //todo: remove #include "../serializer/CSerializer.h" diff --git a/lib/spells/TargetCondition.cpp b/lib/spells/TargetCondition.cpp index cabe2aa87..7b9407afe 100644 --- a/lib/spells/TargetCondition.cpp +++ b/lib/spells/TargetCondition.cpp @@ -17,7 +17,7 @@ #include "../battle/Unit.h" #include "../bonuses/BonusParams.h" #include "../bonuses/BonusList.h" -#include "../json/JsonUtils.h" +#include "../json/JsonBonus.h" #include "../modding/IdentifierStorage.h" #include "../modding/ModUtility.h" #include "../serializer/JsonSerializeFormat.h" diff --git a/lib/spells/effects/Moat.cpp b/lib/spells/effects/Moat.cpp index b51e91b1b..45531d8b1 100644 --- a/lib/spells/effects/Moat.cpp +++ b/lib/spells/effects/Moat.cpp @@ -18,7 +18,7 @@ #include "../../bonuses/Limiters.h" #include "../../battle/IBattleState.h" #include "../../battle/CBattleInfoCallback.h" -#include "../../json/JsonUtils.h" +#include "../../json/JsonBonus.h" #include "../../serializer/JsonSerializeFormat.h" #include "../../networkPacks/PacksForClient.h" #include "../../networkPacks/PacksForClientBattle.h" diff --git a/lib/spells/effects/Timed.cpp b/lib/spells/effects/Timed.cpp index 2fa25d090..a90c903d7 100644 --- a/lib/spells/effects/Timed.cpp +++ b/lib/spells/effects/Timed.cpp @@ -17,7 +17,7 @@ #include "../../battle/IBattleState.h" #include "../../battle/CBattleInfoCallback.h" #include "../../battle/Unit.h" -#include "../../json/JsonUtils.h" +#include "../../json/JsonBonus.h" #include "../../mapObjects/CGHeroInstance.h" #include "../../networkPacks/PacksForClientBattle.h" #include "../../networkPacks/SetStackEffect.h" From 2ea78a588382278bc6be92e8203bfbdd48dada6e Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Wed, 14 Feb 2024 23:30:29 +0100 Subject: [PATCH 117/250] Fix float comparisons Replace this "==" with a more tolerant comparison operation. Floating point numbers should not be tested for equality cpp:S1244 --- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 4 ++-- lib/json/JsonValidator.cpp | 2 +- lib/pathfinder/CGPathNode.h | 2 +- lib/serializer/JsonSerializer.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 863549dd7..310f9d867 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1265,8 +1265,8 @@ bool AINodeStorage::hasBetterChain( && nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength && node.getCost() <= candidateNode->getCost()) { - if(nodeActor->heroFightingStrength == candidateActor->heroFightingStrength - && node.getCost() == candidateNode->getCost() + if(vstd::isAlmostEqual(nodeActor->heroFightingStrength, candidateActor->heroFightingStrength) + && vstd::isAlmostEqual(node.getCost(), candidateNode->getCost()) && &node < candidateNode) { continue; diff --git a/lib/json/JsonValidator.cpp b/lib/json/JsonValidator.cpp index c2941081c..db1ee2196 100644 --- a/lib/json/JsonValidator.cpp +++ b/lib/json/JsonValidator.cpp @@ -232,7 +232,7 @@ namespace std::string multipleOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) { double result = data.Float() / schema.Float(); - if (floor(result) != result) + if (!vstd::isAlmostEqual(floor(result), result)) return validator.makeErrorMessage((boost::format("Value is not divisible by %d") % schema.Float()).str()); return ""; } diff --git a/lib/pathfinder/CGPathNode.h b/lib/pathfinder/CGPathNode.h index 28ee2f920..52e8e7d82 100644 --- a/lib/pathfinder/CGPathNode.h +++ b/lib/pathfinder/CGPathNode.h @@ -102,7 +102,7 @@ struct DLL_LINKAGE CGPathNode STRONG_INLINE void setCost(float value) { - if(value == cost) + if(vstd::isAlmostEqual(value, cost)) return; bool getUpNode = value < cost; diff --git a/lib/serializer/JsonSerializer.cpp b/lib/serializer/JsonSerializer.cpp index 32575e2e0..05b8e34a2 100644 --- a/lib/serializer/JsonSerializer.cpp +++ b/lib/serializer/JsonSerializer.cpp @@ -35,7 +35,7 @@ void JsonSerializer::serializeInternal(const std::string & fieldName, si32 & val void JsonSerializer::serializeInternal(const std::string & fieldName, double & value, const std::optional & defaultValue) { - if(!defaultValue || defaultValue.value() != value) + if(!defaultValue || !vstd::isAlmostEqual(defaultValue.value(), value)) currentObject->operator[](fieldName).Float() = value; } From c0dc1040c28607b0f94e8acb5c0b4241741abad1 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 18 Feb 2024 20:04:31 +0200 Subject: [PATCH 118/250] Fixes crash on attempt to dynamic_cast AbilityCaster to Unit --- lib/spells/effects/Heal.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/spells/effects/Heal.cpp b/lib/spells/effects/Heal.cpp index d3d84d8a9..815d6888c 100644 --- a/lib/spells/effects/Heal.cpp +++ b/lib/spells/effects/Heal.cpp @@ -132,7 +132,8 @@ void Heal::prepareHealEffect(int64_t value, BattleUnitsChanged & pack, BattleLog else if (unitHPgained > 0 && m->caster->getHeroCaster() == nullptr) //Show text about healed HP if healed by unit { MetaString healText; - auto casterUnit = dynamic_cast(m->caster); + auto casterUnitID = m->caster->getCasterUnitId(); + auto casterUnit = m->battle()->battleGetUnitByID(casterUnitID); healText.appendLocalString(EMetaText::GENERAL_TXT, 414); casterUnit->addNameReplacement(healText, false); state->addNameReplacement(healText, false); From af671d109fbcb1a69354c0929c474d37782aefcb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 18 Jan 2024 16:23:12 +0200 Subject: [PATCH 119/250] Made graphical primitive-based UI more configurable --- client/CMakeLists.txt | 2 + client/adventureMap/TurnTimerWidget.cpp | 2 +- client/battle/BattleInterfaceClasses.cpp | 2 +- client/globalLobby/GlobalLobbyLoginWindow.cpp | 1 + client/globalLobby/GlobalLobbyWidget.cpp | 1 + client/gui/InterfaceObjectConfigurable.cpp | 23 ++++- client/gui/InterfaceObjectConfigurable.h | 1 + client/lobby/CSelectionBase.cpp | 2 +- client/mainmenu/CHighScoreScreen.cpp | 2 +- client/widgets/GraphicalPrimitiveCanvas.cpp | 85 ++++++++++++++++ client/widgets/GraphicalPrimitiveCanvas.h | 54 +++++++++++ client/widgets/MiscWidgets.cpp | 60 +++--------- client/widgets/MiscWidgets.h | 30 +----- client/windows/CHeroOverview.cpp | 4 +- client/windows/CSpellWindow.cpp | 2 +- config/widgets/turnOptionsTab.json | 96 ++++++++++++------- 16 files changed, 247 insertions(+), 120 deletions(-) create mode 100644 client/widgets/GraphicalPrimitiveCanvas.cpp create mode 100644 client/widgets/GraphicalPrimitiveCanvas.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 545faa4f5..561dd0201 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -108,6 +108,7 @@ set(client_SRCS widgets/CGarrisonInt.cpp widgets/CreatureCostBox.cpp widgets/ComboBox.cpp + widgets/GraphicalPrimitiveCanvas.cpp widgets/Images.cpp widgets/MiscWidgets.cpp widgets/ObjectLists.cpp @@ -291,6 +292,7 @@ set(client_HEADERS widgets/CGarrisonInt.h widgets/CreatureCostBox.h widgets/ComboBox.h + widgets/GraphicalPrimitiveCanvas.h widgets/Images.h widgets/MiscWidgets.h widgets/ObjectLists.h diff --git a/client/adventureMap/TurnTimerWidget.cpp b/client/adventureMap/TurnTimerWidget.cpp index 95effa225..c97d67f94 100644 --- a/client/adventureMap/TurnTimerWidget.cpp +++ b/client/adventureMap/TurnTimerWidget.cpp @@ -18,7 +18,7 @@ #include "../gui/CGuiHandler.h" #include "../render/Graphics.h" #include "../widgets/Images.h" -#include "../widgets/MiscWidgets.h" +#include "../widgets/GraphicalPrimitiveCanvas.h" #include "../widgets/TextControls.h" #include "../../CCallback.h" diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index e2cd006c1..eb97fdcab 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -34,7 +34,7 @@ #include "../widgets/Buttons.h" #include "../widgets/Images.h" #include "../widgets/TextControls.h" -#include "../widgets/MiscWidgets.h" +#include "../widgets/GraphicalPrimitiveCanvas.h" #include "../windows/CMessage.h" #include "../windows/CCreatureWindow.h" #include "../windows/CSpellWindow.h" diff --git a/client/globalLobby/GlobalLobbyLoginWindow.cpp b/client/globalLobby/GlobalLobbyLoginWindow.cpp index 673941c9f..bf5c68224 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.cpp +++ b/client/globalLobby/GlobalLobbyLoginWindow.cpp @@ -20,6 +20,7 @@ #include "../gui/WindowHandler.h" #include "../widgets/Buttons.h" #include "../widgets/Images.h" +#include "../widgets/GraphicalPrimitiveCanvas.h" #include "../widgets/MiscWidgets.h" #include "../widgets/TextControls.h" diff --git a/client/globalLobby/GlobalLobbyWidget.cpp b/client/globalLobby/GlobalLobbyWidget.cpp index 2be6b21e8..0ffe23254 100644 --- a/client/globalLobby/GlobalLobbyWidget.cpp +++ b/client/globalLobby/GlobalLobbyWidget.cpp @@ -18,6 +18,7 @@ #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" #include "../widgets/Buttons.h" +#include "../widgets/GraphicalPrimitiveCanvas.h" #include "../widgets/MiscWidgets.h" #include "../widgets/ObjectLists.h" #include "../widgets/TextControls.h" diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index 38ad05cad..50d6eedbc 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -22,7 +22,7 @@ #include "../widgets/CComponent.h" #include "../widgets/ComboBox.h" #include "../widgets/Buttons.h" -#include "../widgets/MiscWidgets.h" +#include "../widgets/GraphicalPrimitiveCanvas.h" #include "../widgets/ObjectLists.h" #include "../widgets/Slider.h" #include "../widgets/TextControls.h" @@ -57,6 +57,7 @@ InterfaceObjectConfigurable::InterfaceObjectConfigurable(int used, Point offset) REGISTER_BUILDER("layout", &InterfaceObjectConfigurable::buildLayout); REGISTER_BUILDER("comboBox", &InterfaceObjectConfigurable::buildComboBox); REGISTER_BUILDER("textInput", &InterfaceObjectConfigurable::buildTextInput); + REGISTER_BUILDER("graphicalPrimitive", &InterfaceObjectConfigurable::buildGraphicalPrimitive); REGISTER_BUILDER("transparentFilledRectangle", &InterfaceObjectConfigurable::buildTransparentFilledRectangle); REGISTER_BUILDER("textBox", &InterfaceObjectConfigurable::buildTextBox); } @@ -707,6 +708,26 @@ std::shared_ptr InterfaceObjectConfigurable::buildAnimation(const return anim; } +std::shared_ptr InterfaceObjectConfigurable::buildGraphicalPrimitive(const JsonNode & config) const +{ + logGlobal->debug("Building widget GraphicalPrimitiveCanvas"); + + auto rect = readRect(config["rect"]); + auto widget = std::make_shared(rect); + + for (auto const & entry : config["primitives"].Vector()) + { + auto color = readColor(entry["color"]); + //auto typeString = entry["type"]; + auto pointA = readPosition(entry["a"]); + auto pointB = readPosition(entry["b"]); + + widget->addLine(pointA, pointB, color); + } + + return widget; +} + std::shared_ptr InterfaceObjectConfigurable::buildTransparentFilledRectangle(const JsonNode & config) const { logGlobal->debug("Building widget TransparentFilledRectangle"); diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h index b1ce32b67..b1bbbde93 100644 --- a/client/gui/InterfaceObjectConfigurable.h +++ b/client/gui/InterfaceObjectConfigurable.h @@ -108,6 +108,7 @@ protected: std::shared_ptr buildComboBox(const JsonNode &); std::shared_ptr buildTextInput(const JsonNode &) const; std::shared_ptr buildTransparentFilledRectangle(const JsonNode & config) const; + std::shared_ptr buildGraphicalPrimitive(const JsonNode & config) const; std::shared_ptr buildTextBox(const JsonNode & config) const; //composite widgets diff --git a/client/lobby/CSelectionBase.cpp b/client/lobby/CSelectionBase.cpp index 86bb6ed26..df251d4f9 100644 --- a/client/lobby/CSelectionBase.cpp +++ b/client/lobby/CSelectionBase.cpp @@ -29,7 +29,7 @@ #include "../mainmenu/CMainMenu.h" #include "../widgets/Buttons.h" #include "../widgets/CComponent.h" -#include "../widgets/MiscWidgets.h" +#include "../widgets/GraphicalPrimitiveCanvas.h" #include "../widgets/ObjectLists.h" #include "../widgets/Slider.h" #include "../widgets/TextControls.h" diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 4abcc712b..4d18d4c04 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -17,7 +17,7 @@ #include "../widgets/TextControls.h" #include "../widgets/Buttons.h" #include "../widgets/Images.h" -#include "../widgets/MiscWidgets.h" +#include "../widgets/GraphicalPrimitiveCanvas.h" #include "../windows/InfoWindows.h" #include "../render/Canvas.h" diff --git a/client/widgets/GraphicalPrimitiveCanvas.cpp b/client/widgets/GraphicalPrimitiveCanvas.cpp new file mode 100644 index 000000000..e7734edde --- /dev/null +++ b/client/widgets/GraphicalPrimitiveCanvas.cpp @@ -0,0 +1,85 @@ +/* + * MiscWidgets.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 "GraphicalPrimitiveCanvas.h" + +#include "../render/Canvas.h" + +GraphicalPrimitiveCanvas::GraphicalPrimitiveCanvas(Rect dimensions) +{ + pos = dimensions + pos.topLeft(); +} + +void GraphicalPrimitiveCanvas::showAll(Canvas & to) +{ + auto const & translatePoint = [this](const Point & input){ + int x = input.x < 0 ? pos.w + input.x : input.x; + int y = input.y < 0 ? pos.h + input.y : input.y; + + return Point(x,y); + }; + + for (auto const & entry : primitives) + { + switch (entry.type) + { + case PrimitiveType::LINE: + { + to.drawLine(pos.topLeft() + translatePoint(entry.a), pos.topLeft() + translatePoint(entry.b), entry.color, entry.color); + break; + } + case PrimitiveType::FILLED_BOX: + { + to.drawColorBlended(Rect(pos.topLeft() + translatePoint(entry.a), translatePoint(entry.b)), entry.color); + break; + } + case PrimitiveType::RECTANGLE: + { + to.drawBorder(Rect(pos.topLeft() + translatePoint(entry.a), translatePoint(entry.b)), entry.color); + break; + } + } + } +} + +void GraphicalPrimitiveCanvas::addLine(const Point & from, const Point & to, const ColorRGBA & color) +{ + primitives.push_back({color, from, to, PrimitiveType::LINE}); +} + +void GraphicalPrimitiveCanvas::addBox(const Point & topLeft, const Point & size, const ColorRGBA & color) +{ + primitives.push_back({color, topLeft, size, PrimitiveType::FILLED_BOX}); +} + +void GraphicalPrimitiveCanvas::addRectangle(const Point & topLeft, const Point & size, const ColorRGBA & color) +{ + primitives.push_back({color, topLeft, size, PrimitiveType::RECTANGLE}); +} + +TransparentFilledRectangle::TransparentFilledRectangle(Rect position, ColorRGBA color) : + GraphicalPrimitiveCanvas(position) +{ + addBox(Point(0,0), Point(-1, -1), color); +} + +TransparentFilledRectangle::TransparentFilledRectangle(Rect position, ColorRGBA color, ColorRGBA colorLine, int width) : + GraphicalPrimitiveCanvas(position) +{ + addBox(Point(0,0), Point(-1, -1), color); + for (int i = 0; i < width; ++i) + addRectangle(Point(i,i), Point(-1-i, -1-i), colorLine); +} + +SimpleLine::SimpleLine(Point pos1, Point pos2, ColorRGBA color) : + GraphicalPrimitiveCanvas(Rect(pos1, pos2 - pos1)) +{ + addLine(Point(0,0), Point(-1, -1), color); +} diff --git a/client/widgets/GraphicalPrimitiveCanvas.h b/client/widgets/GraphicalPrimitiveCanvas.h new file mode 100644 index 000000000..2cf508b94 --- /dev/null +++ b/client/widgets/GraphicalPrimitiveCanvas.h @@ -0,0 +1,54 @@ +/* + * GraphicalPrimitiveCanvas.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/CIntObject.h" + +class GraphicalPrimitiveCanvas : public CIntObject +{ + enum class PrimitiveType + { + LINE, + RECTANGLE, + FILLED_BOX + }; + + struct PrimitiveEntry + { + ColorRGBA color; + Point a; + Point b; + PrimitiveType type; + }; + + std::vector primitives; + + void showAll(Canvas & to) override; + +public: + GraphicalPrimitiveCanvas(Rect position); + + void addLine(const Point & from, const Point & to, const ColorRGBA & color); + void addBox(const Point & topLeft, const Point & size, const ColorRGBA & color); + void addRectangle(const Point & topLeft, const Point & size, const ColorRGBA & color); +}; + +class TransparentFilledRectangle : public GraphicalPrimitiveCanvas +{ +public: + TransparentFilledRectangle(Rect position, ColorRGBA color); + TransparentFilledRectangle(Rect position, ColorRGBA color, ColorRGBA colorLine, int width = 1); +}; + +class SimpleLine : public GraphicalPrimitiveCanvas +{ +public: + SimpleLine(Point pos1, Point pos2, ColorRGBA color); +}; diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp index c44bc288c..584392267 100644 --- a/client/widgets/MiscWidgets.cpp +++ b/client/widgets/MiscWidgets.cpp @@ -21,8 +21,9 @@ #include "../gui/WindowHandler.h" #include "../eventsSDL/InputHandler.h" #include "../windows/CTradeWindow.h" -#include "../widgets/TextControls.h" #include "../widgets/CGarrisonInt.h" +#include "../widgets/GraphicalPrimitiveCanvas.h" +#include "../widgets/TextControls.h" #include "../windows/CCastleInterface.h" #include "../windows/InfoWindows.h" #include "../render/Canvas.h" @@ -663,54 +664,13 @@ void CCreaturePic::setAmount(int newAmount) amount->setText(""); } -TransparentFilledRectangle::TransparentFilledRectangle(Rect position, ColorRGBA color) : - color(color), colorLine(ColorRGBA()), drawLine(false), lineWidth(0) -{ - pos = position + pos.topLeft(); -} - -TransparentFilledRectangle::TransparentFilledRectangle(Rect position, ColorRGBA color, ColorRGBA colorLine, int width) : - color(color), colorLine(colorLine), drawLine(true), lineWidth(width) -{ - pos = position + pos.topLeft(); -} - -void TransparentFilledRectangle::setDrawBorder(bool on) -{ - drawLine = on; -} - -bool TransparentFilledRectangle::getDrawBorder() -{ - return drawLine; -} - -void TransparentFilledRectangle::setBorderWidth(int width) -{ - lineWidth = width; -} - -void TransparentFilledRectangle::showAll(Canvas & to) -{ - to.drawColorBlended(pos, color); - if(drawLine) - to.drawBorder(pos, colorLine, lineWidth); -} - -SimpleLine::SimpleLine(Point pos1, Point pos2, ColorRGBA color) : - pos1(pos1), pos2(pos2), color(color) -{} - -void SimpleLine::showAll(Canvas & to) -{ - to.drawLine(pos1 + pos.topLeft(), pos2 + pos.topLeft(), color, color); -} - SelectableSlot::SelectableSlot(Rect area, Point oversize, const int width) : LRClickableAreaWTextComp(area) + , selected(false) { - selection = std::make_unique( - Rect(area.topLeft() - oversize, area.dimensions() + oversize * 2), Colors::TRANSPARENCY, Colors::YELLOW, width); + OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + + selection = std::make_shared( Rect(-oversize, area.dimensions() + oversize * 2), Colors::TRANSPARENCY, Colors::YELLOW, width); selectSlot(false); } @@ -726,15 +686,17 @@ SelectableSlot::SelectableSlot(Rect area, const int width) void SelectableSlot::selectSlot(bool on) { - selection->setDrawBorder(on); + selection->setEnabled(on); + selected = on; } bool SelectableSlot::isSelected() const { - return selection->getDrawBorder(); + return selected; } void SelectableSlot::setSelectionWidth(int width) { - selection->setBorderWidth(width); + OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + selection = std::make_shared( selection->pos - pos.topLeft(), Colors::TRANSPARENCY, Colors::YELLOW, width); } diff --git a/client/widgets/MiscWidgets.h b/client/widgets/MiscWidgets.h index acdfcd2dd..36a741755 100644 --- a/client/widgets/MiscWidgets.h +++ b/client/widgets/MiscWidgets.h @@ -33,6 +33,7 @@ class CCreatureAnim; class CComponent; class CAnimImage; class LRClickableArea; +class TransparentFilledRectangle; /// Shows a text by moving the mouse cursor over the object class CHoverableArea: public virtual CIntObject @@ -248,35 +249,10 @@ public: MoraleLuckBox(bool Morale, const Rect &r, bool Small=false); }; -class TransparentFilledRectangle : public CIntObject -{ - ColorRGBA color; - ColorRGBA colorLine; - bool drawLine; - int lineWidth; - -public: - TransparentFilledRectangle(Rect position, ColorRGBA color); - TransparentFilledRectangle(Rect position, ColorRGBA color, ColorRGBA colorLine, int width = 1); - void setDrawBorder(bool on); - bool getDrawBorder(); - void setBorderWidth(int width); - void showAll(Canvas & to) override; -}; - -class SimpleLine : public CIntObject -{ - Point pos1; - Point pos2; - ColorRGBA color; -public: - SimpleLine(Point pos1, Point pos2, ColorRGBA color); - void showAll(Canvas & to) override; -}; - class SelectableSlot : public LRClickableAreaWTextComp { - std::unique_ptr selection; + std::shared_ptr selection; + bool selected; public: SelectableSlot(Rect area, Point oversize, const int width); diff --git a/client/windows/CHeroOverview.cpp b/client/windows/CHeroOverview.cpp index 00b522cc2..2a5412dc1 100644 --- a/client/windows/CHeroOverview.cpp +++ b/client/windows/CHeroOverview.cpp @@ -20,7 +20,7 @@ #include "../widgets/CComponent.h" #include "../widgets/Images.h" #include "../widgets/TextControls.h" -#include "../widgets/MiscWidgets.h" +#include "../widgets/GraphicalPrimitiveCanvas.h" #include "../../lib/GameSettings.h" #include "../../lib/CGeneralTextHandler.h" @@ -234,4 +234,4 @@ void CHeroOverview::genControls() labelSpellsNames.push_back(std::make_shared(302 + (292 / 2) + 3 * borderOffset + 32 + borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 3, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*CGI->spellh)[spell]->getNameTranslated())); i++; } -} \ No newline at end of file +} diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 2ecc9acf4..8c3ab46e9 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -25,7 +25,7 @@ #include "../gui/CGuiHandler.h" #include "../gui/Shortcut.h" #include "../gui/WindowHandler.h" -#include "../widgets/MiscWidgets.h" +#include "../widgets/GraphicalPrimitiveCanvas.h" #include "../widgets/CComponent.h" #include "../widgets/TextControls.h" #include "../adventureMap/AdventureMapInterface.h" diff --git a/config/widgets/turnOptionsTab.json b/config/widgets/turnOptionsTab.json index d31b26b79..65ff54687 100644 --- a/config/widgets/turnOptionsTab.json +++ b/config/widgets/turnOptionsTab.json @@ -66,15 +66,7 @@ "rect": {"x": 60, "y": 48, "w": 320, "h": 0}, "adoptHeight": true }, - -// { -// "type": "label", -// "font": "medium", -// "alignment": "center", -// "color": "yellow", -// "text": "vcmi.optionsTab.selectPreset", -// "position": {"x": 105, "y": 100} -// }, + { "type" : "dropDownTimers", "name": "timerPresetSelector", @@ -94,36 +86,68 @@ "color" : "blue", "rect": {"x" : 64, "y" : 394, "w": 316, "h": 124} }, + { - "type": "transparentFilledRectangle", + "type": "graphicalPrimitive", "rect": {"x" : 64, "y" : 394, "w": 316, "h": 124}, - "color": [0, 0, 0, 0], - "colorLine": [64, 80, 128, 128] - }, - { - "type": "transparentFilledRectangle", - "rect": {"x" : 65, "y" : 416, "w": 314, "h": 1}, - "color": [0, 0, 0, 0], - "colorLine": [80, 96, 160, 128] - }, - { - "type": "transparentFilledRectangle", - "rect": {"x" : 65, "y" : 417, "w": 314, "h": 1}, - "color": [0, 0, 0, 0], - "colorLine": [32, 40, 128, 128] - }, - { - "type": "transparentFilledRectangle", - "rect": {"x" : 65, "y" : 466, "w": 314, "h": 1}, - "color": [0, 0, 0, 0], - "colorLine": [80, 96, 160, 128] - }, - { - "type": "transparentFilledRectangle", - "rect": {"x" : 65, "y" : 467, "w": 314, "h": 1}, - "color": [0, 0, 0, 0], - "colorLine": [32, 40, 128, 128] + "primitives" : [ + // Top line + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 32 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 48 ] } + + // Left line + { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : 0, "y" : -2}, "color" : [ 255, 255, 255, 32 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 2}, "b" : { "x" : 1, "y" : -2}, "color" : [ 0, 0, 0, 48 ] } + + // Right line + { "type" : "line", "a" : { "x" : -2, "y" : 2}, "b" : { "x" : -2, "y" : -3}, "color" : [ 255, 255, 255, 24 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 1}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] }, + + // Central separator + { "type" : "line", "a" : { "x" : 1, "y" : 22}, "b" : { "x" : -2, "y" : 22}, "color" : [ 255, 255, 255, 64 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 23}, "b" : { "x" : -2, "y" : 23}, "color" : [ 0, 0, 0, 64 ] }, + + // 2nd Central separator + { "type" : "line", "a" : { "x" : 1, "y" : 72}, "b" : { "x" : -2, "y" : 72}, "color" : [ 255, 255, 255, 64 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 73}, "b" : { "x" : -2, "y" : 73}, "color" : [ 0, 0, 0, 64 ] }, + + // Bottom line + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 24 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 255, 255, 255, 48 ] }, + ] }, + + +// { +// "type": "transparentFilledRectangle", +// "rect": {"x" : 64, "y" : 394, "w": 316, "h": 124}, +// "color": [0, 0, 0, 0], +// "colorLine": [64, 80, 128, 128] +// }, +// { +// "type": "transparentFilledRectangle", +// "rect": {"x" : 65, "y" : 416, "w": 314, "h": 1}, +// "color": [0, 0, 0, 0], +// "colorLine": [80, 96, 160, 128] +// }, +// { +// "type": "transparentFilledRectangle", +// "rect": {"x" : 65, "y" : 417, "w": 314, "h": 1}, +// "color": [0, 0, 0, 0], +// "colorLine": [32, 40, 128, 128] +// }, +// { +// "type": "transparentFilledRectangle", +// "rect": {"x" : 65, "y" : 466, "w": 314, "h": 1}, +// "color": [0, 0, 0, 0], +// "colorLine": [80, 96, 160, 128] +// }, +// { +// "type": "transparentFilledRectangle", +// "rect": {"x" : 65, "y" : 467, "w": 314, "h": 1}, +// "color": [0, 0, 0, 0], +// "colorLine": [32, 40, 128, 128] +// }, { "type" : "verticalLayout66", "customType" : "labelTitle", From 8b60275a6e7c75f034d20377439f88ce01ae9672 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 18 Feb 2024 21:02:24 +0200 Subject: [PATCH 120/250] Hero exchange in town is now activated by clicking on hero portrait --- client/windows/CCastleInterface.cpp | 20 ++++++-------------- client/windows/CCastleInterface.h | 1 - 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 16fdd2365..a07fa29bd 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -427,7 +427,11 @@ void CHeroGSlot::clickPressed(const Point & cursorPosition) if(hero && isSelected()) { setHighlight(false); - LOCPLINT->openHeroWindow(hero); + + if(other->hero) + LOCPLINT->showHeroExchange(hero->id, other->hero->id); + else + LOCPLINT->openHeroWindow(hero); } else if(other->hero && other->isSelected()) { @@ -515,14 +519,6 @@ void HeroSlots::update() visitingHero->set(town->visitingHero); } -void HeroSlots::splitClicked() -{ - if(!!town->visitingHero && town->garrisonHero && (visitingHero->isSelected() || garrisonedHero->isSelected())) - { - LOCPLINT->showHeroExchange(town->visitingHero->id, town->garrisonHero->id); - } -} - void HeroSlots::swapArmies() { bool allow = true; @@ -1217,11 +1213,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst exit = std::make_shared(Point(744, 544), AnimationPath::builtin("TSBTNS"), CButton::tooltip(CGI->generaltexth->tcommands[8]), [&](){close();}, EShortcut::GLOBAL_RETURN); exit->setImageOrder(4, 5, 6, 7); - auto split = std::make_shared(Point(744, 382), AnimationPath::builtin("TSBTNS"), CButton::tooltip(CGI->generaltexth->tcommands[3]), [&]() - { - garr->splitClick(); - heroes->splitClicked(); - }); + auto split = std::make_shared(Point(744, 382), AnimationPath::builtin("TSBTNS"), CButton::tooltip(CGI->generaltexth->tcommands[3]), [this]() { garr->splitClick(); }); garr->addSplitBtn(split); Rect barRect(9, 182, 732, 18); diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index 1b7884737..f883c62dd 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -133,7 +133,6 @@ public: HeroSlots(const CGTownInstance * town, Point garrPos, Point visitPos, std::shared_ptr Garrison, bool ShowEmpty); ~HeroSlots(); - void splitClicked(); //for hero meeting only (splitting stacks is handled by garrison int) void update(); void swapArmies(); //exchange garrisoned and visiting heroes or move hero to\from garrison }; From 14e3c762c01a661f304cc845ba3ea4641ba31f97 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 18 Feb 2024 21:18:55 +0200 Subject: [PATCH 121/250] Fix slot selection in markets --- client/widgets/GraphicalPrimitiveCanvas.cpp | 2 +- client/widgets/MiscWidgets.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/widgets/GraphicalPrimitiveCanvas.cpp b/client/widgets/GraphicalPrimitiveCanvas.cpp index e7734edde..23fa62b1f 100644 --- a/client/widgets/GraphicalPrimitiveCanvas.cpp +++ b/client/widgets/GraphicalPrimitiveCanvas.cpp @@ -75,7 +75,7 @@ TransparentFilledRectangle::TransparentFilledRectangle(Rect position, ColorRGBA { addBox(Point(0,0), Point(-1, -1), color); for (int i = 0; i < width; ++i) - addRectangle(Point(i,i), Point(-1-i, -1-i), colorLine); + addRectangle(Point(i,i), Point(-1-i*2, -1-i*2), colorLine); } SimpleLine::SimpleLine(Point pos1, Point pos2, ColorRGBA color) : diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp index 584392267..fa6c174d1 100644 --- a/client/widgets/MiscWidgets.cpp +++ b/client/widgets/MiscWidgets.cpp @@ -699,4 +699,5 @@ void SelectableSlot::setSelectionWidth(int width) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); selection = std::make_shared( selection->pos - pos.topLeft(), Colors::TRANSPARENCY, Colors::YELLOW, width); + selectSlot(selected); } From ef8ff009730f3bd979a1a7199596f497c2508ad1 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 18 Feb 2024 22:15:31 +0200 Subject: [PATCH 122/250] Unified common primitive-based UI elements --- client/gui/InterfaceObjectConfigurable.cpp | 25 ++++++-- client/widgets/GraphicalPrimitiveCanvas.h | 2 + config/widgets/commonPrimitives.json | 53 +++++++++++++++ config/widgets/extraOptionsTab.json | 10 +-- config/widgets/mapOverview.json | 56 ++++++---------- config/widgets/turnOptionsTab.json | 75 +++++----------------- 6 files changed, 116 insertions(+), 105 deletions(-) create mode 100644 config/widgets/commonPrimitives.json diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index 50d6eedbc..89887d0ff 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -114,8 +114,20 @@ void InterfaceObjectConfigurable::build(const JsonNode &config) { if (!config["library"].isNull()) { - const JsonNode library(JsonPath::fromJson(config["library"])); - loadCustomBuilders(library); + if (config["library"].isString()) + { + const JsonNode library(JsonPath::fromJson(config["library"])); + loadCustomBuilders(library); + } + + if (config["library"].isVector()) + { + for (auto const & entry : config["library"].Vector()) + { + const JsonNode library(JsonPath::fromJson(entry)); + loadCustomBuilders(library); + } + } } loadCustomBuilders(config["customTypes"]); @@ -718,11 +730,16 @@ std::shared_ptr InterfaceObjectConfigurable::buildGraphicalPrimitive for (auto const & entry : config["primitives"].Vector()) { auto color = readColor(entry["color"]); - //auto typeString = entry["type"]; + auto typeString = entry["type"].String(); auto pointA = readPosition(entry["a"]); auto pointB = readPosition(entry["b"]); - widget->addLine(pointA, pointB, color); + if (typeString == "line") + widget->addLine(pointA, pointB, color); + if (typeString == "filledBox") + widget->addBox(pointA, pointB, color); + if (typeString == "rectangle") + widget->addRectangle(pointA, pointB, color); } return widget; diff --git a/client/widgets/GraphicalPrimitiveCanvas.h b/client/widgets/GraphicalPrimitiveCanvas.h index 2cf508b94..21087f566 100644 --- a/client/widgets/GraphicalPrimitiveCanvas.h +++ b/client/widgets/GraphicalPrimitiveCanvas.h @@ -13,6 +13,7 @@ class GraphicalPrimitiveCanvas : public CIntObject { +public: enum class PrimitiveType { LINE, @@ -20,6 +21,7 @@ class GraphicalPrimitiveCanvas : public CIntObject FILLED_BOX }; +private: struct PrimitiveEntry { ColorRGBA color; diff --git a/config/widgets/commonPrimitives.json b/config/widgets/commonPrimitives.json new file mode 100644 index 000000000..8520efe25 --- /dev/null +++ b/config/widgets/commonPrimitives.json @@ -0,0 +1,53 @@ +{ + "boxWithNoBackground" : { + "type": "graphicalPrimitive", + "primitives" : [ + // Top line + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 32 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 48 ] } + + // Left line + { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : 0, "y" : -2}, "color" : [ 255, 255, 255, 32 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 2}, "b" : { "x" : 1, "y" : -2}, "color" : [ 0, 0, 0, 48 ] } + + // Right line + { "type" : "line", "a" : { "x" : -2, "y" : 2}, "b" : { "x" : -2, "y" : -3}, "color" : [ 255, 255, 255, 24 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 1}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] }, + + // Bottom line + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 24 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 255, 255, 255, 48 ] }, + ] + }, + + "horizontalLine" : { + "type": "graphicalPrimitive", + "primitives" : [ + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 64 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 64 ] }, + ] + } + + "boxWithBackground" : { + "type": "graphicalPrimitive", + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 75 ] }, + + // Top line + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 32 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 48 ] } + + // Left line + { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : 0, "y" : -2}, "color" : [ 255, 255, 255, 32 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 2}, "b" : { "x" : 1, "y" : -2}, "color" : [ 0, 0, 0, 48 ] } + + // Right line + { "type" : "line", "a" : { "x" : -2, "y" : 2}, "b" : { "x" : -2, "y" : -3}, "color" : [ 255, 255, 255, 24 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 1}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] }, + + // Bottom line + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 24 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 255, 255, 255, 48 ] }, + ] + }, +} diff --git a/config/widgets/extraOptionsTab.json b/config/widgets/extraOptionsTab.json index 91b41bced..f0b939192 100644 --- a/config/widgets/extraOptionsTab.json +++ b/config/widgets/extraOptionsTab.json @@ -37,27 +37,27 @@ }, { "type": "transparentFilledRectangle", - "rect": {"x": 54, "y": 127, "w": 335, "h": 1}, + "rect": {"x": 54, "y": 127, "w": 335, "h": 2}, "color": [24, 41, 90, 255] }, { "type": "transparentFilledRectangle", - "rect": {"x": 158, "y": 90, "w": 2, "h": 37}, + "rect": {"x": 159, "y": 90, "w": 2, "h": 38}, "color": [24, 41, 90, 255] }, { "type": "transparentFilledRectangle", - "rect": {"x": 234, "y": 90, "w": 2, "h": 37}, + "rect": {"x": 235, "y": 90, "w": 2, "h": 38}, "color": [24, 41, 90, 255] }, { "type": "transparentFilledRectangle", - "rect": {"x": 310, "y": 90, "w": 2, "h": 37}, + "rect": {"x": 311, "y": 90, "w": 2, "h": 38}, "color": [24, 41, 90, 255] }, { "type": "transparentFilledRectangle", - "rect": {"x": 55, "y": 556, "w": 334, "h": 18}, + "rect": {"x": 55, "y": 556, "w": 335, "h": 19}, "color": [24, 41, 90, 255] }, { diff --git a/config/widgets/mapOverview.json b/config/widgets/mapOverview.json index d451afda4..9aff4aab7 100644 --- a/config/widgets/mapOverview.json +++ b/config/widgets/mapOverview.json @@ -1,4 +1,6 @@ { + "library" : "config/widgets/commonPrimitives.json", + "items": [ { @@ -8,10 +10,8 @@ "rect": {"w": 428, "h": 379} }, { - "type": "transparentFilledRectangle", - "rect": {"x": 5, "y": 5, "w": 418, "h": 20}, - "color": [0, 0, 0, 75], - "colorLine": [128, 100, 75, 255] + "type": "boxWithBackground", + "rect": {"x": 5, "y": 5, "w": 418, "h": 20} }, { "type": "label", @@ -22,10 +22,8 @@ "position": {"x": 214, "y": 15} }, { - "type": "transparentFilledRectangle", - "rect": {"x": 5, "y": 30, "w": 418, "h": 20}, - "color": [0, 0, 0, 75], - "colorLine": [128, 100, 75, 255] + "type": "boxWithBackground", + "rect": {"x": 5, "y": 30, "w": 418, "h": 20} }, { "type": "label", @@ -37,10 +35,8 @@ "position": {"x": 214, "y": 40} }, { - "type": "transparentFilledRectangle", - "rect": {"x": 5, "y": 55, "w": 418, "h": 20}, - "color": [0, 0, 0, 75], - "colorLine": [128, 100, 75, 255] + "type": "boxWithBackground", + "rect": {"x": 5, "y": 55, "w": 418, "h": 20} }, { "type": "label", @@ -51,10 +47,8 @@ "position": {"x": 214, "y": 65} }, { - "type": "transparentFilledRectangle", - "rect": {"x": 29, "y": 79, "w": 171, "h": 171}, - "color": [0, 0, 0, 255], - "colorLine": [128, 100, 75, 255] + "type": "boxWithBackground", + "rect": {"x": 29, "y": 79, "w": 171, "h": 171} }, { "type": "label", @@ -70,10 +64,8 @@ "rect": {"x": 30, "y": 80, "w": 169, "h": 169} }, { - "type": "transparentFilledRectangle", - "rect": {"x": 228, "y": 79, "w": 171, "h": 171}, - "color": [0, 0, 0, 255], - "colorLine": [128, 100, 75, 255] + "type": "boxWithBackground", + "rect": {"x": 228, "y": 79, "w": 171, "h": 171} }, { "type": "label", @@ -90,10 +82,8 @@ "rect": {"x": 229, "y": 80, "w": 169, "h": 169} }, { - "type": "transparentFilledRectangle", - "rect": {"x": 5, "y": 254, "w": 418, "h": 20}, - "color": [0, 0, 0, 75], - "colorLine": [128, 100, 75, 255] + "type": "boxWithBackground", + "rect": {"x": 5, "y": 254, "w": 418, "h": 20} }, { "type": "label", @@ -104,10 +94,8 @@ "position": {"x": 214, "y": 264} }, { - "type": "transparentFilledRectangle", - "rect": {"x": 5, "y": 279, "w": 418, "h": 20}, - "color": [0, 0, 0, 75], - "colorLine": [128, 100, 75, 255] + "type": "boxWithBackground", + "rect": {"x": 5, "y": 279, "w": 418, "h": 20} }, { "type": "label", @@ -119,10 +107,8 @@ "position": {"x": 214, "y": 289} }, { - "type": "transparentFilledRectangle", - "rect": {"x": 5, "y": 304, "w": 418, "h": 20}, - "color": [0, 0, 0, 75], - "colorLine": [128, 100, 75, 255] + "type": "boxWithBackground", + "rect": {"x": 5, "y": 304, "w": 418, "h": 20} }, { "type": "label", @@ -133,10 +119,8 @@ "position": {"x": 214, "y": 314} }, { - "type": "transparentFilledRectangle", - "rect": {"x": 5, "y": 329, "w": 418, "h": 45}, - "color": [0, 0, 0, 75], - "colorLine": [128, 100, 75, 255] + "type": "boxWithBackground", + "rect": {"x": 5, "y": 329, "w": 418, "h": 45} }, { "type": "textBox", diff --git a/config/widgets/turnOptionsTab.json b/config/widgets/turnOptionsTab.json index 65ff54687..d642b1b35 100644 --- a/config/widgets/turnOptionsTab.json +++ b/config/widgets/turnOptionsTab.json @@ -1,5 +1,8 @@ { - "library" : "config/widgets/turnOptionsDropdownLibrary.json", + "library" : [ + "config/widgets/turnOptionsDropdownLibrary.json", + "config/widgets/commonPrimitives.json", + ], "customTypes" : { "verticalLayout66" : { @@ -88,66 +91,18 @@ }, { - "type": "graphicalPrimitive", - "rect": {"x" : 64, "y" : 394, "w": 316, "h": 124}, - "primitives" : [ - // Top line - { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 32 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 48 ] } - - // Left line - { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : 0, "y" : -2}, "color" : [ 255, 255, 255, 32 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 2}, "b" : { "x" : 1, "y" : -2}, "color" : [ 0, 0, 0, 48 ] } - - // Right line - { "type" : "line", "a" : { "x" : -2, "y" : 2}, "b" : { "x" : -2, "y" : -3}, "color" : [ 255, 255, 255, 24 ] }, - { "type" : "line", "a" : { "x" : -1, "y" : 1}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] }, - - // Central separator - { "type" : "line", "a" : { "x" : 1, "y" : 22}, "b" : { "x" : -2, "y" : 22}, "color" : [ 255, 255, 255, 64 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 23}, "b" : { "x" : -2, "y" : 23}, "color" : [ 0, 0, 0, 64 ] }, - - // 2nd Central separator - { "type" : "line", "a" : { "x" : 1, "y" : 72}, "b" : { "x" : -2, "y" : 72}, "color" : [ 255, 255, 255, 64 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 73}, "b" : { "x" : -2, "y" : 73}, "color" : [ 0, 0, 0, 64 ] }, - - // Bottom line - { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 24 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 255, 255, 255, 48 ] }, - ] + "type": "boxWithNoBackground", + "rect": {"x" : 64, "y" : 394, "w": 316, "h": 124} }, - - -// { -// "type": "transparentFilledRectangle", -// "rect": {"x" : 64, "y" : 394, "w": 316, "h": 124}, -// "color": [0, 0, 0, 0], -// "colorLine": [64, 80, 128, 128] -// }, -// { -// "type": "transparentFilledRectangle", -// "rect": {"x" : 65, "y" : 416, "w": 314, "h": 1}, -// "color": [0, 0, 0, 0], -// "colorLine": [80, 96, 160, 128] -// }, -// { -// "type": "transparentFilledRectangle", -// "rect": {"x" : 65, "y" : 417, "w": 314, "h": 1}, -// "color": [0, 0, 0, 0], -// "colorLine": [32, 40, 128, 128] -// }, -// { -// "type": "transparentFilledRectangle", -// "rect": {"x" : 65, "y" : 466, "w": 314, "h": 1}, -// "color": [0, 0, 0, 0], -// "colorLine": [80, 96, 160, 128] -// }, -// { -// "type": "transparentFilledRectangle", -// "rect": {"x" : 65, "y" : 467, "w": 314, "h": 1}, -// "color": [0, 0, 0, 0], -// "colorLine": [32, 40, 128, 128] -// }, + { + "type": "horizontalLine", + "rect": {"x" : 65, "y" : 416, "w": 314, "h": 2} + }, + { + "type": "horizontalLine", + "rect": {"x" : 65, "y" : 466, "w": 314, "h": 2} + }, + { "type" : "verticalLayout66", "customType" : "labelTitle", From fc1e9f70f99eb9fd6417d43f65703cc93d7fef2a Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Sun, 18 Feb 2024 23:16:11 +0100 Subject: [PATCH 123/250] Fix float comparison --- test/JsonComparer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/JsonComparer.cpp b/test/JsonComparer.cpp index 99c3c0c99..7df764eb0 100644 --- a/test/JsonComparer.cpp +++ b/test/JsonComparer.cpp @@ -45,7 +45,7 @@ bool JsonComparer::isEmpty(const JsonNode & value) case JsonNode::JsonType::DATA_BOOL: return !value.Bool(); case JsonNode::JsonType::DATA_FLOAT: - return value.Float() == 0; + return vstd::isAlmostEqual(value.Float(), 0); case JsonNode::JsonType::DATA_INTEGER: return value.Integer() == 0; case JsonNode::JsonType::DATA_STRING: From 06a56a0ec3fd686c74397259fb5cfea4882ba34d Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Sun, 18 Feb 2024 23:32:12 +0100 Subject: [PATCH 124/250] Try to fix compilation error: /home/runner/work/vcmi/vcmi/test/../Global.h:700:36: error: no matching function for call to 'max' const Floating relativeEpsilon = std::max(std::abs(left), std::abs(right)) * epsilon; ^~~~~~~~ /home/runner/work/vcmi/vcmi/test/JsonComparer.cpp:48:16: note: in instantiation of function template specialization 'vstd::isAlmostEqual' requested here return vstd::isAlmostEqual(value.Float(), 0); ^ --- test/JsonComparer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/JsonComparer.cpp b/test/JsonComparer.cpp index 7df764eb0..46a9b00da 100644 --- a/test/JsonComparer.cpp +++ b/test/JsonComparer.cpp @@ -45,7 +45,7 @@ bool JsonComparer::isEmpty(const JsonNode & value) case JsonNode::JsonType::DATA_BOOL: return !value.Bool(); case JsonNode::JsonType::DATA_FLOAT: - return vstd::isAlmostEqual(value.Float(), 0); + return vstd::isAlmostEqual(value.Float(), 0.0); case JsonNode::JsonType::DATA_INTEGER: return value.Integer() == 0; case JsonNode::JsonType::DATA_STRING: From 10b35ceecb992efbe52c3152fca948629f824c7e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Feb 2024 00:35:21 +0200 Subject: [PATCH 125/250] Replaced assertions with exceptions for invalid input in RNG --- include/vstd/RNG.h | 8 ++++++-- lib/CRandomGenerator.cpp | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/include/vstd/RNG.h b/include/vstd/RNG.h index 72c4d5006..60e1dedc0 100644 --- a/include/vstd/RNG.h +++ b/include/vstd/RNG.h @@ -36,14 +36,18 @@ namespace RandomGeneratorUtil template auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) { - assert(!container.empty()); + if(container.empty()) + throw std::runtime_error("Unable to select random item from empty container!"); + return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)()); } template auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) { - assert(!container.empty()); + if(container.empty()) + throw std::runtime_error("Unable to select random item from empty container!"); + return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)()); } diff --git a/lib/CRandomGenerator.cpp b/lib/CRandomGenerator.cpp index 037207c76..22ff8c78e 100644 --- a/lib/CRandomGenerator.cpp +++ b/lib/CRandomGenerator.cpp @@ -37,25 +37,25 @@ void CRandomGenerator::resetSeed() TRandI CRandomGenerator::getIntRange(int lower, int upper) { - assert(lower <= upper); - return std::bind(TIntDist(lower, upper), std::ref(rand)); + if (lower <= upper) + return std::bind(TIntDist(lower, upper), std::ref(rand)); + throw std::runtime_error("Invalid range provided: " + std::to_string(lower) + " ... " + std::to_string(upper)); } vstd::TRandI64 CRandomGenerator::getInt64Range(int64_t lower, int64_t upper) { - assert(lower <= upper); - return std::bind(TInt64Dist(lower, upper), std::ref(rand)); + if(lower <= upper) + return std::bind(TInt64Dist(lower, upper), std::ref(rand)); + throw std::runtime_error("Invalid range provided: " + std::to_string(lower) + " ... " + std::to_string(upper)); } int CRandomGenerator::nextInt(int upper) { - assert(0 <= upper); return getIntRange(0, upper)(); } int CRandomGenerator::nextInt(int lower, int upper) { - assert(lower <= upper); return getIntRange(lower, upper)(); } @@ -66,19 +66,19 @@ int CRandomGenerator::nextInt() vstd::TRand CRandomGenerator::getDoubleRange(double lower, double upper) { - assert(lower <= upper); - return std::bind(TRealDist(lower, upper), std::ref(rand)); + if(lower <= upper) + return std::bind(TRealDist(lower, upper), std::ref(rand)); + throw std::runtime_error("Invalid range provided: " + std::to_string(lower) + " ... " + std::to_string(upper)); + } double CRandomGenerator::nextDouble(double upper) { - assert(0 <= upper); return getDoubleRange(0, upper)(); } double CRandomGenerator::nextDouble(double lower, double upper) { - assert(lower <= upper); return getDoubleRange(lower, upper)(); } From fc252bb9ebd71386d97cbcb9541c2ff4afc9df96 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Feb 2024 00:36:08 +0200 Subject: [PATCH 126/250] Random town names are now guaranteed to be unique on the map --- lib/CTownHandler.cpp | 5 --- lib/CTownHandler.h | 1 - lib/constants/EntityIdentifiers.cpp | 5 +++ lib/constants/EntityIdentifiers.h | 2 ++ lib/gameState/CGameState.cpp | 56 ++++++++++++++++++++++++----- lib/gameState/CGameState.h | 1 + 6 files changed, 56 insertions(+), 14 deletions(-) diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index df3e33f57..2544c4a9c 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -243,11 +243,6 @@ CTown::~CTown() str.dellNull(); } -std::string CTown::getRandomNameTranslated(size_t index) const -{ - return VLC->generaltexth->translate(getRandomNameTextID(index)); -} - std::string CTown::getRandomNameTextID(size_t index) const { return TextIdentifier("faction", faction->modScope, faction->identifier, "randomName", index).get(); diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 97bfa325f..5af1d661b 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -217,7 +217,6 @@ public: void setGreeting(BuildingSubID::EBuildingSubID subID, const std::string & message) const; //may affect only mutable field BuildingID getBuildingType(BuildingSubID::EBuildingSubID subID) const; - std::string getRandomNameTranslated(size_t index) const; std::string getRandomNameTextID(size_t index) const; size_t getRandomNamesCount() const; diff --git a/lib/constants/EntityIdentifiers.cpp b/lib/constants/EntityIdentifiers.cpp index 8fb932e2e..d1d2b66c6 100644 --- a/lib/constants/EntityIdentifiers.cpp +++ b/lib/constants/EntityIdentifiers.cpp @@ -458,6 +458,11 @@ std::string FactionID::entityType() return "faction"; } +const CFaction * FactionID::toFaction() const +{ + return dynamic_cast(toEntity(VLC)); +} + const Faction * FactionID::toEntity(const Services * service) const { return service->factions()->getByIndex(num); diff --git a/lib/constants/EntityIdentifiers.h b/lib/constants/EntityIdentifiers.h index 070d26492..387994014 100644 --- a/lib/constants/EntityIdentifiers.h +++ b/lib/constants/EntityIdentifiers.h @@ -24,6 +24,7 @@ class CHero; class CHeroClass; class HeroClass; class HeroTypeService; +class CFaction; class Faction; class Skill; class RoadType; @@ -261,6 +262,7 @@ public: static si32 decode(const std::string& identifier); static std::string encode(const si32 index); + const CFaction * toFaction() const; const Faction * toEntity(const Services * service) const; static std::string entityType(); diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index cfa55c4d7..36a92a96f 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -222,6 +222,7 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, Load::Prog initHeroes(); initStartingBonus(); initTowns(); + initTownNames(); placeHeroesInTowns(); initMapObjects(); buildBonusSystemTree(); @@ -763,6 +764,51 @@ void CGameState::initStartingBonus() } } +void CGameState::initTownNames() +{ + std::map> availableNames; + for (auto const & faction : VLC->townh->getDefaultAllowed()) + { + std::vector potentialNames; + if (faction.toFaction()->town->getRandomNamesCount() > 0) + { + for (int i = 0; i < faction.toFaction()->town->getRandomNamesCount(); ++i) + potentialNames.push_back(i); + + availableNames[faction] = potentialNames; + } + } + + for (auto & elem : map->towns) + { + CGTownInstance * vti =(elem); + assert(vti->town); + + if(!vti->getNameTextID().empty()) + continue; + + FactionID faction = vti->getFaction(); + + if (availableNames.empty()) + { + logGlobal->warn("Failed to find available name for a random town!"); + vti->setNameTextId("core.genrltxt.508"); // Unnamed + continue; + } + + // If town has no available names (for example - all were picked) - pick names from some other faction that still has names available + if (!availableNames.count(faction)) + faction = RandomGeneratorUtil::nextItem(availableNames, getRandomGenerator())->first; + + auto nameIt = RandomGeneratorUtil::nextItem(availableNames[faction], getRandomGenerator()); + vti->setNameTextId(faction.toFaction()->town->getRandomNameTextID(*nameIt)); + + availableNames[faction].erase(nameIt); + if (availableNames[faction].empty()) + availableNames.erase(faction); + } +} + void CGameState::initTowns() { logGlobal->debug("\tTowns"); @@ -777,15 +823,9 @@ void CGameState::initTowns() map->townUniversitySkills.push_back(SecondarySkill(SecondarySkill::EARTH_MAGIC)); for (auto & elem : map->towns) - { - CGTownInstance * vti =(elem); - assert(vti->town); - - if(vti->getNameTextID().empty()) { - size_t nameID = getRandomGenerator().nextInt(vti->getTown()->getRandomNamesCount() - 1); - vti->setNameTextId(vti->getTown()->getRandomNameTextID(nameID)); - } + CGTownInstance * vti =(elem); + assert(vti->town); static const BuildingID basicDwellings[] = { BuildingID::DWELL_FIRST, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 }; static const BuildingID upgradedDwellings[] = { BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP, BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP }; diff --git a/lib/gameState/CGameState.h b/lib/gameState/CGameState.h index f3de9cdd7..ce2a4b102 100644 --- a/lib/gameState/CGameState.h +++ b/lib/gameState/CGameState.h @@ -212,6 +212,7 @@ private: void initFogOfWar(); void initStartingBonus(); void initTowns(); + void initTownNames(); void initMapObjects(); void initVisitingAndGarrisonedHeroes(); void initCampaign(); From fe918de2dfbda87807e89ec0fc642c49c7614fde Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Feb 2024 00:36:21 +0200 Subject: [PATCH 127/250] Remove invalid assertion --- client/CServerHandler.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index f8ca5e4e2..0ac30b561 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -832,10 +832,7 @@ void CServerHandler::onPacketReceived(const std::shared_ptr boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); if(getState() == EClientState::DISCONNECTING) - { - assert(0); //Should not be possible - socket must be closed at this point return; - } CPack * pack = logicConnection->retrievePack(message); ServerHandlerCPackVisitor visitor(*this); From 81e44711a24105b22aedde10628be9008ac5e1f0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Feb 2024 13:45:50 +0200 Subject: [PATCH 128/250] Remove no longer needed workaround --- lib/CConsoleHandler.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/CConsoleHandler.cpp b/lib/CConsoleHandler.cpp index 72de08baa..a0eca4814 100644 --- a/lib/CConsoleHandler.cpp +++ b/lib/CConsoleHandler.cpp @@ -143,13 +143,6 @@ LONG WINAPI onUnhandledException(EXCEPTION_POINTERS* exception) HANDLE dfile = CreateFileA(mname, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); logGlobal->error("Crash info will be put in %s", mname); - // flush loggers - std::string padding(1000, '@'); - - logGlobal->error(padding); - logAi->error(padding); - logNetwork->error(padding); - auto dumpType = MiniDumpWithDataSegs; if(settings["general"]["extraDump"].Bool()) From f620a073890dad19ab99247e37af099aa2f966f4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Feb 2024 13:46:07 +0200 Subject: [PATCH 129/250] Add protocol validation --- lib/network/NetworkConnection.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index 01e20d951..7b31ca04b 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -49,6 +49,12 @@ void NetworkConnection::onHeaderReceived(const boost::system::error_code & ecHea return; } + if (messageSize == 0) + { + listener.onDisconnected(shared_from_this(), "Zero-sized packet!"); + return; + } + boost::asio::async_read(*socket, readBuffer, boost::asio::transfer_exactly(messageSize), From 779625415f33388160ae5aa928fb3508fc13d815 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Feb 2024 13:47:06 +0200 Subject: [PATCH 130/250] Guard against concurrent writes on same asio::socket instance --- lib/network/NetworkConnection.cpp | 1 + lib/network/NetworkConnection.h | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index 7b31ca04b..e2239d8bd 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -88,6 +88,7 @@ void NetworkConnection::sendPacket(const std::vector & message) // 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::mutex::scoped_lock lock(writeMutex); boost::asio::write(*socket, boost::asio::buffer(messageSize), ec ); boost::asio::write(*socket, boost::asio::buffer(message), ec ); diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h index beaaba376..a6d3b721d 100644 --- a/lib/network/NetworkConnection.h +++ b/lib/network/NetworkConnection.h @@ -19,6 +19,7 @@ class NetworkConnection : public INetworkConnection, public std::enable_shared_f static const int messageMaxSize = 64 * 1024 * 1024; // arbitrary size to prevent potential massive allocation if we receive garbage input std::shared_ptr socket; + boost::mutex writeMutex; NetworkBuffer readBuffer; INetworkConnectionListener & listener; From c513dc1bc791b6b8ba90705b7401caa08d988a9d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Feb 2024 15:58:44 +0200 Subject: [PATCH 131/250] Fix code style & formatting --- lib/gameState/CGameState.cpp | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 36a92a96f..9806cd060 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -767,21 +767,20 @@ void CGameState::initStartingBonus() void CGameState::initTownNames() { std::map> availableNames; - for (auto const & faction : VLC->townh->getDefaultAllowed()) + for(const auto & faction : VLC->townh->getDefaultAllowed()) { std::vector potentialNames; - if (faction.toFaction()->town->getRandomNamesCount() > 0) + if(faction.toFaction()->town->getRandomNamesCount() > 0) { - for (int i = 0; i < faction.toFaction()->town->getRandomNamesCount(); ++i) + for(int i = 0; i < faction.toFaction()->town->getRandomNamesCount(); ++i) potentialNames.push_back(i); availableNames[faction] = potentialNames; } } - for (auto & elem : map->towns) + for(auto & vti : map->towns) { - CGTownInstance * vti =(elem); assert(vti->town); if(!vti->getNameTextID().empty()) @@ -789,7 +788,7 @@ void CGameState::initTownNames() FactionID faction = vti->getFaction(); - if (availableNames.empty()) + if(availableNames.empty()) { logGlobal->warn("Failed to find available name for a random town!"); vti->setNameTextId("core.genrltxt.508"); // Unnamed @@ -797,14 +796,14 @@ void CGameState::initTownNames() } // If town has no available names (for example - all were picked) - pick names from some other faction that still has names available - if (!availableNames.count(faction)) + if(!availableNames.count(faction)) faction = RandomGeneratorUtil::nextItem(availableNames, getRandomGenerator())->first; auto nameIt = RandomGeneratorUtil::nextItem(availableNames[faction], getRandomGenerator()); vti->setNameTextId(faction.toFaction()->town->getRandomNameTextID(*nameIt)); availableNames[faction].erase(nameIt); - if (availableNames[faction].empty()) + if(availableNames[faction].empty()) availableNames.erase(faction); } } @@ -822,14 +821,13 @@ void CGameState::initTowns() map->townUniversitySkills.push_back(SecondarySkill(SecondarySkill::WATER_MAGIC)); map->townUniversitySkills.push_back(SecondarySkill(SecondarySkill::EARTH_MAGIC)); - for (auto & elem : map->towns) - { - CGTownInstance * vti =(elem); - assert(vti->town); + for (auto & vti : map->towns) + { + assert(vti->town); - static const BuildingID basicDwellings[] = { BuildingID::DWELL_FIRST, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 }; - static const BuildingID upgradedDwellings[] = { BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP, BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP }; - static const BuildingID hordes[] = { BuildingID::HORDE_PLACEHOLDER1, BuildingID::HORDE_PLACEHOLDER2, BuildingID::HORDE_PLACEHOLDER3, BuildingID::HORDE_PLACEHOLDER4, BuildingID::HORDE_PLACEHOLDER5, BuildingID::HORDE_PLACEHOLDER6, BuildingID::HORDE_PLACEHOLDER7 }; + constexpr std::array basicDwellings = { BuildingID::DWELL_FIRST, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 }; + constexpr std::array upgradedDwellings = { BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP, BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP }; + constexpr std::array hordes = { BuildingID::HORDE_PLACEHOLDER1, BuildingID::HORDE_PLACEHOLDER2, BuildingID::HORDE_PLACEHOLDER3, BuildingID::HORDE_PLACEHOLDER4, BuildingID::HORDE_PLACEHOLDER5, BuildingID::HORDE_PLACEHOLDER6, BuildingID::HORDE_PLACEHOLDER7 }; //init buildings if(vstd::contains(vti->builtBuildings, BuildingID::DEFAULT)) //give standard set of buildings From 82eea1abc162f75d99250bad72add29c71e46045 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Feb 2024 16:49:59 +0200 Subject: [PATCH 132/250] Remove unused variable --- client/windows/CCastleInterface.h | 1 - 1 file changed, 1 deletion(-) diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index f883c62dd..32c1d426c 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -226,7 +226,6 @@ class CCastleInterface : public CStatusbarWindow, public IGarrisonHolder std::shared_ptr fort; std::shared_ptr exit; - std::shared_ptr split; std::shared_ptr fastTownHall; std::shared_ptr fastArmyPurchase; std::shared_ptr fastMarket; From bd901cb0017633f0bf0bb61a974d895060cac725 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 18 Feb 2024 22:47:40 +0200 Subject: [PATCH 133/250] Replaced lines in settings with primitives --- Mods/vcmi/Data/settingsWindow/checkBoxEmpty.png | Bin 130 -> 0 bytes Mods/vcmi/Data/settingsWindow/lineHorizontal.png | Bin 90 -> 0 bytes Mods/vcmi/Data/settingsWindow/lineVertical.png | Bin 91 -> 0 bytes client/widgets/GraphicalPrimitiveCanvas.cpp | 2 +- client/widgets/GraphicalPrimitiveCanvas.h | 2 -- config/widgets/commonPrimitives.json | 10 +++++++++- config/widgets/settings/adventureOptionsTab.json | 8 +++++--- config/widgets/settings/battleOptionsTab.json | 11 ++++++----- config/widgets/settings/generalOptionsTab.json | 8 +++++--- config/widgets/settings/library.json | 5 +++-- .../widgets/settings/settingsMainContainer.json | 13 +++++++------ config/widgets/turnOptionsTab.json | 6 ++---- 12 files changed, 38 insertions(+), 27 deletions(-) delete mode 100644 Mods/vcmi/Data/settingsWindow/checkBoxEmpty.png delete mode 100644 Mods/vcmi/Data/settingsWindow/lineHorizontal.png delete mode 100644 Mods/vcmi/Data/settingsWindow/lineVertical.png diff --git a/Mods/vcmi/Data/settingsWindow/checkBoxEmpty.png b/Mods/vcmi/Data/settingsWindow/checkBoxEmpty.png deleted file mode 100644 index f2eae977fdaa3de5e826237005dd57e558049f78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Eu!3HE}2mdz#Ql6eJjv*C{$qY?xjR!d{Y$z`K z?vTKy&}N}<^mLiShK;jqEL0{~emNw;CsBG}1+$y1!K;Qq_8zN*D~yXlLJlTg2l*sE bxM?!%w~48l;=PX>XefiHtDnm{r-UW|7*i-0 diff --git a/Mods/vcmi/Data/settingsWindow/lineHorizontal.png b/Mods/vcmi/Data/settingsWindow/lineHorizontal.png deleted file mode 100644 index 5eb70f1ff0379a41b4fe5c2f9616a42157f2be25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^4M5Dy!3HFwrP%iZDMe2g$B+ufH79>7-6v)G%Bfn}w(!_f=fr=SCUHx3vIVCg!0ABYO(*OVf diff --git a/Mods/vcmi/Data/settingsWindow/lineVertical.png b/Mods/vcmi/Data/settingsWindow/lineVertical.png deleted file mode 100644 index f5c337938b93a1f20cf02b17f1d86377971f088a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91 zcmeAS@N?(olHy`uVBq!ia0vp^%s|}0!3HGH@mxL$q?9~e978G?lM76P^E3QDXC)*g pWGoO)pJIGU@@Y?zo8@8|2AkGZ6Kb#RuLi1S@O1TaS?83{1OVq;8fE|h diff --git a/client/widgets/GraphicalPrimitiveCanvas.cpp b/client/widgets/GraphicalPrimitiveCanvas.cpp index 23fa62b1f..c37325c55 100644 --- a/client/widgets/GraphicalPrimitiveCanvas.cpp +++ b/client/widgets/GraphicalPrimitiveCanvas.cpp @@ -1,5 +1,5 @@ /* - * MiscWidgets.cpp, part of VCMI engine + * GraphicalPrimitiveCanvas.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/client/widgets/GraphicalPrimitiveCanvas.h b/client/widgets/GraphicalPrimitiveCanvas.h index 21087f566..2cf508b94 100644 --- a/client/widgets/GraphicalPrimitiveCanvas.h +++ b/client/widgets/GraphicalPrimitiveCanvas.h @@ -13,7 +13,6 @@ class GraphicalPrimitiveCanvas : public CIntObject { -public: enum class PrimitiveType { LINE, @@ -21,7 +20,6 @@ public: FILLED_BOX }; -private: struct PrimitiveEntry { ColorRGBA color; diff --git a/config/widgets/commonPrimitives.json b/config/widgets/commonPrimitives.json index 8520efe25..8edd2deee 100644 --- a/config/widgets/commonPrimitives.json +++ b/config/widgets/commonPrimitives.json @@ -26,7 +26,15 @@ { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 64 ] }, ] - } + }, + + "verticalLine" : { + "type": "graphicalPrimitive", + "primitives" : [ + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 0}, "b" : { "x" : 1, "y" : -1}, "color" : [ 0, 0, 0, 64 ] }, + ] + }, "boxWithBackground" : { "type": "graphicalPrimitive", diff --git a/config/widgets/settings/adventureOptionsTab.json b/config/widgets/settings/adventureOptionsTab.json index faa0d01ab..57886e5e2 100644 --- a/config/widgets/settings/adventureOptionsTab.json +++ b/config/widgets/settings/adventureOptionsTab.json @@ -1,12 +1,14 @@ { - "library" : "config/widgets/settings/library.json", + "library" : [ + "config/widgets/settings/library.json", + "config/widgets/commonPrimitives.json", + ], "items": [ { "name": "lineLabelsEnd", - "type": "texture", - "image": "settingsWindow/lineHorizontal", + "type": "horizontalLine", "rect": { "x" : 5, "y" : 229, "w": 365, "h": 3} }, /////////////////////////////////////// Left section - Hero Speed and Map Scrolling diff --git a/config/widgets/settings/battleOptionsTab.json b/config/widgets/settings/battleOptionsTab.json index 4db262212..08480233f 100644 --- a/config/widgets/settings/battleOptionsTab.json +++ b/config/widgets/settings/battleOptionsTab.json @@ -1,18 +1,19 @@ { - "library" : "config/widgets/settings/library.json", + "library" : [ + "config/widgets/settings/library.json", + "config/widgets/commonPrimitives.json", + ], "items": [ { "name": "lineCreatureInfo", - "type": "texture", - "image": "settingsWindow/lineHorizontal", + "type": "horizontalLine", "rect": { "x" : 5, "y" : 289, "w": 365, "h": 3} }, { "name": "lineAnimationSpeed", - "type": "texture", - "image": "settingsWindow/lineHorizontal", + "type": "horizontalLine", "rect": { "x" : 5, "y" : 349, "w": 365, "h": 3} }, { diff --git a/config/widgets/settings/generalOptionsTab.json b/config/widgets/settings/generalOptionsTab.json index 6a70d457e..03b744272 100644 --- a/config/widgets/settings/generalOptionsTab.json +++ b/config/widgets/settings/generalOptionsTab.json @@ -1,12 +1,14 @@ { - "library" : "config/widgets/settings/library.json", + "library" : [ + "config/widgets/settings/library.json", + "config/widgets/commonPrimitives.json", + ], "items": [ { "name": "lineLabelsEnd", - "type": "texture", - "image": "settingsWindow/lineHorizontal", + "type": "horizontalLine", "rect": { "x" : 5, "y" : 349, "w": 365, "h": 3} }, { diff --git a/config/widgets/settings/library.json b/config/widgets/settings/library.json index c5ea07f95..21096db72 100644 --- a/config/widgets/settings/library.json +++ b/config/widgets/settings/library.json @@ -35,8 +35,9 @@ ] }, "checkboxFake" : { - "type": "picture", - "image": "settingsWindow/checkBoxEmpty" + "type": "boxWithBackground", + "rect": { "x" : 0, "y" : 0, "w": 32, "h": 24} + }, "audioSlider" : { "type": "slider", diff --git a/config/widgets/settings/settingsMainContainer.json b/config/widgets/settings/settingsMainContainer.json index 0e495d73f..48665a5ab 100644 --- a/config/widgets/settings/settingsMainContainer.json +++ b/config/widgets/settings/settingsMainContainer.json @@ -1,4 +1,8 @@ { + "library" : [ + "config/widgets/commonPrimitives.json" + ], + "items": [ { @@ -9,14 +13,12 @@ }, { "name": "lineTabs", - "type": "texture", - "image": "settingsWindow/lineHorizontal", + "type": "horizontalLine", "rect": { "x" : 10, "y" : 45, "w": 580, "h": 3} }, { "name": "lineColumns", - "type": "texture", - "image": "settingsWindow/lineVertical", + "type": "verticalLine", "rect": { "x" : 370, "y" : 50, "w": 3, "h": 420} }, @@ -91,8 +93,7 @@ { "name": "lineButtons", - "type": "texture", - "image": "settingsWindow/lineHorizontal", + "type": "horizontalLine", "rect": { "x" : 375, "y" : 289, "w": 220, "h": 3} }, { diff --git a/config/widgets/turnOptionsTab.json b/config/widgets/turnOptionsTab.json index d642b1b35..0ba76719a 100644 --- a/config/widgets/turnOptionsTab.json +++ b/config/widgets/turnOptionsTab.json @@ -33,10 +33,8 @@ "offset": {"x": 0, "y": 0} }, "timeInputBackground" : { - "type": "transparentFilledRectangle", - "rect": {"x": 0, "y": 0, "w": 86, "h": 23}, - "color": [0, 0, 0, 128], - "colorLine": [64, 80, 128, 128] + "type": "boxWithBackground", + "rect": {"x": 0, "y": 0, "w": 86, "h": 23} } }, From 5053500965feb97404069106095a3f1c7625578f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Feb 2024 18:02:44 +0200 Subject: [PATCH 134/250] Fix json formatting --- config/widgets/commonPrimitives.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/config/widgets/commonPrimitives.json b/config/widgets/commonPrimitives.json index 8edd2deee..aab498284 100644 --- a/config/widgets/commonPrimitives.json +++ b/config/widgets/commonPrimitives.json @@ -4,11 +4,11 @@ "primitives" : [ // Top line { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 32 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 48 ] } + { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 48 ] }, // Left line { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : 0, "y" : -2}, "color" : [ 255, 255, 255, 32 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 2}, "b" : { "x" : 1, "y" : -2}, "color" : [ 0, 0, 0, 48 ] } + { "type" : "line", "a" : { "x" : 1, "y" : 2}, "b" : { "x" : 1, "y" : -2}, "color" : [ 0, 0, 0, 48 ] }, // Right line { "type" : "line", "a" : { "x" : -2, "y" : 2}, "b" : { "x" : -2, "y" : -3}, "color" : [ 255, 255, 255, 24 ] }, @@ -24,7 +24,7 @@ "type": "graphicalPrimitive", "primitives" : [ { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 64 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 64 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 64 ] } ] }, @@ -32,7 +32,7 @@ "type": "graphicalPrimitive", "primitives" : [ { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 0}, "b" : { "x" : 1, "y" : -1}, "color" : [ 0, 0, 0, 64 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 0}, "b" : { "x" : 1, "y" : -1}, "color" : [ 0, 0, 0, 64 ] } ] }, @@ -43,11 +43,11 @@ // Top line { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 32 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 48 ] } + { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 48 ] }, // Left line { "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : 0, "y" : -2}, "color" : [ 255, 255, 255, 32 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 2}, "b" : { "x" : 1, "y" : -2}, "color" : [ 0, 0, 0, 48 ] } + { "type" : "line", "a" : { "x" : 1, "y" : 2}, "b" : { "x" : 1, "y" : -2}, "color" : [ 0, 0, 0, 48 ] }, // Right line { "type" : "line", "a" : { "x" : -2, "y" : 2}, "b" : { "x" : -2, "y" : -3}, "color" : [ 255, 255, 255, 24 ] }, @@ -55,7 +55,7 @@ // Bottom line { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 24 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 255, 255, 255, 48 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 255, 255, 255, 48 ] } ] }, } From 73a1a188d99f8bc8a44d5f3f61b1f912759c6367 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Fri, 23 Feb 2024 19:26:12 +0100 Subject: [PATCH 135/250] Update metainfo to conform to AppStream 1.0 spec --- launcher/eu.vcmi.VCMI.metainfo.xml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/launcher/eu.vcmi.VCMI.metainfo.xml b/launcher/eu.vcmi.VCMI.metainfo.xml index f65788fd7..f73dc3e01 100644 --- a/launcher/eu.vcmi.VCMI.metainfo.xml +++ b/launcher/eu.vcmi.VCMI.metainfo.xml @@ -5,8 +5,10 @@ Open-source game engine for Heroes of Might and Magic III Herní engine s otevřeným kódem pro Heroes of Might and Magic III Open-Source-Spielengine für Heroes of Might and Magic III - VCMI Team - Tým VCMI + + VCMI Team + Tým VCMI + CC-BY-SA-4.0 GPL-2.0-or-later @@ -75,6 +77,7 @@ https://play-lh.googleusercontent.com/wD6xJK2nuzVyKUFODfQs2SEbUuSyEglojpp_FE6GeAuzdA_R44Dp6gTyN70KCwpRtD9M=w2560-h1440 + vcmilauncher.desktop From a79f1f45f6127e76b928394dbd4a4baa4e855454 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 24 Feb 2024 15:51:36 +0100 Subject: [PATCH 136/250] firewall rules for client --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3040e89a4..ceadb6fd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -799,10 +799,12 @@ if(WIN32) set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " CreateShortCut \\\"$DESKTOP\\\\VCMI.lnk\\\" \\\"$INSTDIR\\\\${VCMI_MAIN_EXECUTABLE}.exe\\\" ExecShell '' 'netsh' 'advfirewall firewall add rule name=vcmi_server dir=in action=allow program=\\\"$INSTDIR\\\\vcmi_server.exe\\\" enable=yes profile=public,private' SW_HIDE + ExecShell '' 'netsh' 'advfirewall firewall add rule name=vcmi_client dir=in action=allow program=\\\"$INSTDIR\\\\vcmi_client.exe\\\" enable=yes profile=public,private' SW_HIDE ") set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS " Delete \\\"$DESKTOP\\\\VCMI.lnk\\\" ExecShell '' 'netsh' 'advfirewall firewall delete rule name=vcmi_server' SW_HIDE + ExecShell '' 'netsh' 'advfirewall firewall delete rule name=vcmi_client' SW_HIDE ") # Strip MinGW CPack target if build configuration without debug info From 2e8801084d44272125b596a5fe1999fd4d6ccd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sun, 25 Feb 2024 11:40:01 +0100 Subject: [PATCH 137/250] Clean up RMG logs --- lib/rmg/CZonePlacer.cpp | 20 ++++++++++++++++++-- lib/rmg/Zone.cpp | 3 +-- lib/rmg/modificators/Modificator.cpp | 4 ++-- lib/rmg/modificators/QuestArtifactPlacer.cpp | 8 ++++---- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 9765a7b4e..75f248bb3 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -24,6 +24,8 @@ VCMI_LIB_NAMESPACE_BEGIN +//#define ZONE_PLACEMENT_LOG true + class CRandomGenerator; CZonePlacer::CZonePlacer(RmgMap & map) @@ -248,7 +250,8 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) } //TODO: toggle with a flag - logGlobal->info("Initial zone grid:"); +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Initial zone grid:"); for (size_t x = 0; x < gridSize; ++x) { std::string s; @@ -263,8 +266,9 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand) s += " -- "; } } - logGlobal->info(s); + logGlobal->trace(s); } +#endif //Set initial position for zones - random position in square centered around (x, y) for (size_t x = 0; x < gridSize; ++x) @@ -372,7 +376,9 @@ void CZonePlacer::placeZones(CRandomGenerator * rand) bestSolution[zone.second] = zone.second->getCenter(); } +#ifdef ZONE_PLACEMENT_LOG logGlobal->trace("Total distance between zones after this iteration: %2.4f, Total overlap: %2.4f, Improved: %s", totalDistance, totalOverlap , improvement); +#endif return improvement; }; @@ -419,7 +425,9 @@ void CZonePlacer::placeZones(CRandomGenerator * rand) for(const auto & zone : zones) //finalize zone positions { zone.second->setPos (cords (bestSolution[zone.second])); +#ifdef ZONE_PLACEMENT_LOG logGlobal->trace("Placed zone %d at relative position %s and coordinates %s", zone.first, zone.second->getCenter().toString(), zone.second->getPos().toString()); +#endif } } @@ -688,7 +696,9 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista return lhs.first > rhs.first; //Largest dispalcement first }); +#ifdef ZONE_PLACEMENT_LOG logGlobal->trace("Worst misplacement/movement ratio: %3.2f", misplacedZones.front().first); +#endif if (misplacedZones.size() >= 2) { @@ -721,7 +731,9 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista } if (secondZone) { +#ifdef ZONE_PLACEMENT_LOG logGlobal->trace("Swapping two misplaced zones %d and %d", firstZone->getId(), secondZone->getId()); +#endif auto firstCenter = firstZone->getCenter(); auto secondCenter = secondZone->getCenter(); @@ -764,7 +776,9 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista { float3 vec = targetZone->getCenter() - ourCenter; float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize; +#ifdef ZONE_PLACEMENT_LOG logGlobal->trace("Trying to move zone %d %s towards %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); +#endif misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size } @@ -792,7 +806,9 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista { float3 vec = ourCenter - targetZone->getCenter(); float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize; +#ifdef ZONE_PLACEMENT_LOG logGlobal->trace("Trying to move zone %d %s away from %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); +#endif misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated } diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index dad4f22d7..af42e6d88 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -278,7 +278,7 @@ void Zone::fractalize() float blockDistance = minDistance * spanFactor; //More obstacles in the Underground freeDistance = freeDistance * marginFactor; vstd::amax(freeDistance, 4 * 4); - logGlobal->info("Zone %d: treasureValue %d blockDistance: %2.f, freeDistance: %2.f", getId(), treasureValue, blockDistance, freeDistance); + logGlobal->trace("Zone %d: treasureValue %d blockDistance: %2.f, freeDistance: %2.f", getId(), treasureValue, blockDistance, freeDistance); if(type != ETemplateZoneType::JUNCTION) { @@ -381,7 +381,6 @@ void Zone::initModificators() { modificator->init(); } - logGlobal->info("Zone %d modificators initialized", getId()); } CRandomGenerator& Zone::getRand() diff --git a/lib/rmg/modificators/Modificator.cpp b/lib/rmg/modificators/Modificator.cpp index 9f2bfa004..d37b75764 100644 --- a/lib/rmg/modificators/Modificator.cpp +++ b/lib/rmg/modificators/Modificator.cpp @@ -80,7 +80,7 @@ void Modificator::run() if(!finished) { - logGlobal->info("Modificator zone %d - %s - started", zone.getId(), getName()); + logGlobal->trace("Modificator zone %d - %s - started", zone.getId(), getName()); CStopWatch processTime; try { @@ -94,7 +94,7 @@ void Modificator::run() dump(); #endif finished = true; - logGlobal->info("Modificator zone %d - %s - done (%d ms)", zone.getId(), getName(), processTime.getDiff()); + logGlobal->trace("Modificator zone %d - %s - done (%d ms)", zone.getId(), getName(), processTime.getDiff()); } } diff --git a/lib/rmg/modificators/QuestArtifactPlacer.cpp b/lib/rmg/modificators/QuestArtifactPlacer.cpp index 812aa1df0..684c16ffd 100644 --- a/lib/rmg/modificators/QuestArtifactPlacer.cpp +++ b/lib/rmg/modificators/QuestArtifactPlacer.cpp @@ -40,14 +40,14 @@ void QuestArtifactPlacer::addQuestArtZone(std::shared_ptr otherZone) void QuestArtifactPlacer::addQuestArtifact(const ArtifactID& id) { - logGlobal->info("Need to place quest artifact %s", VLC->artifacts()->getById(id)->getNameTranslated()); + logGlobal->trace("Need to place quest artifact %s", VLC->artifacts()->getById(id)->getNameTranslated()); RecursiveLock lock(externalAccessMutex); questArtifactsToPlace.emplace_back(id); } void QuestArtifactPlacer::removeQuestArtifact(const ArtifactID& id) { - logGlobal->info("Will not try to place quest artifact %s", VLC->artifacts()->getById(id)->getNameTranslated()); + logGlobal->trace("Will not try to place quest artifact %s", VLC->artifacts()->getById(id)->getNameTranslated()); RecursiveLock lock(externalAccessMutex); vstd::erase_if_present(questArtifactsToPlace, id); } @@ -76,7 +76,7 @@ void QuestArtifactPlacer::findZonesForQuestArts() } } - logGlobal->info("Number of nearby zones suitable for quest artifacts: %d", questArtZones.size()); + logGlobal->trace("Number of nearby zones suitable for quest artifacts: %d", questArtZones.size()); } void QuestArtifactPlacer::placeQuestArtifacts(CRandomGenerator & rand) @@ -92,7 +92,7 @@ void QuestArtifactPlacer::placeQuestArtifacts(CRandomGenerator & rand) continue; auto artifactToReplace = *RandomGeneratorUtil::nextItem(artifactsToReplace, rand); - logGlobal->info("Replacing %s at %s with the quest artifact %s", + logGlobal->trace("Replacing %s at %s with the quest artifact %s", artifactToReplace->getObjectName(), artifactToReplace->getPosition().toString(), VLC->artifacts()->getById(artifactToPlace)->getNameTranslated()); From af0207470d8bf240e86f9732ea25349cdd9421bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sun, 25 Feb 2024 12:25:23 +0100 Subject: [PATCH 138/250] - Increased minimal obstacle density on surface - Decreased minimal obstacle density in the underground --- lib/rmg/Zone.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index af42e6d88..ff9adb095 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -240,7 +240,7 @@ void Zone::fractalize() //Squared float minDistance = 9 * 9; float freeDistance = pos.z ? (10 * 10) : 6 * 6; - float spanFactor = (pos.z ? 0.25 : 0.5f); //Narrower passages in the Underground + float spanFactor = (pos.z ? 0.3f : 0.45f); //Narrower passages in the Underground float marginFactor = 1.0f; int treasureValue = 0; @@ -263,10 +263,10 @@ void Zone::fractalize() // A quater at max density marginFactor = (0.25f + ((std::max(0, (600 - treasureValue))) / (600.f - 400)) * 0.75f); } - else if (treasureValue < 125) + else if (treasureValue < 100) { //Dense obstacles - spanFactor *= (treasureValue / 125.f); + spanFactor *= (treasureValue / 100.f); vstd::amax(spanFactor, 0.15f); } if (treasureDensity <= 10) From 6901945b6e29880ac4daf96916d4894055d8352f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 25 Feb 2024 20:05:28 +0200 Subject: [PATCH 139/250] Fix possible thread race on sending packet from two threads --- lib/network/NetworkConnection.cpp | 1 - lib/network/NetworkConnection.h | 1 - lib/serializer/Connection.cpp | 2 ++ lib/serializer/Connection.h | 2 ++ 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index e2239d8bd..7b31ca04b 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -88,7 +88,6 @@ void NetworkConnection::sendPacket(const std::vector & message) // 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::mutex::scoped_lock lock(writeMutex); boost::asio::write(*socket, boost::asio::buffer(messageSize), ec ); boost::asio::write(*socket, boost::asio::buffer(message), ec ); diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h index a6d3b721d..beaaba376 100644 --- a/lib/network/NetworkConnection.h +++ b/lib/network/NetworkConnection.h @@ -19,7 +19,6 @@ class NetworkConnection : public INetworkConnection, public std::enable_shared_f static const int messageMaxSize = 64 * 1024 * 1024; // arbitrary size to prevent potential massive allocation if we receive garbage input std::shared_ptr socket; - boost::mutex writeMutex; NetworkBuffer readBuffer; INetworkConnectionListener & listener; diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index c72921d2f..d8521f0a1 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -70,6 +70,8 @@ CConnection::~CConnection() = default; void CConnection::sendPack(const CPack * pack) { + boost::mutex::scoped_lock lock(writeMutex); + auto connectionPtr = networkConnection.lock(); if (!connectionPtr) diff --git a/lib/serializer/Connection.h b/lib/serializer/Connection.h index 3d14f261b..68d23c34e 100644 --- a/lib/serializer/Connection.h +++ b/lib/serializer/Connection.h @@ -32,6 +32,8 @@ class DLL_LINKAGE CConnection : boost::noncopyable std::unique_ptr deserializer; std::unique_ptr serializer; + boost::mutex writeMutex; + void disableStackSendingByID(); void enableStackSendingByID(); void disableSmartPointerSerialization(); From bc9e96125251b5c788571026ad6e0714e352b453 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 25 Feb 2024 20:05:52 +0200 Subject: [PATCH 140/250] Better checks for incoming data --- lib/serializer/Connection.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index d8521f0a1..f76b77262 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -94,7 +94,13 @@ CPack * CConnection::retrievePack(const std::vector & data) *deserializer & result; - logNetwork->trace("Received CPack of type %s", (result ? typeid(*result).name() : "nullptr")); + if (result == nullptr) + throw std::runtime_error("Failed to retrieve pack!"); + + if (packReader->position != data.size()) + throw std::runtime_error("Failed to retrieve pack! Not all data has been read!"); + + logNetwork->trace("Received CPack of type %s", typeid(*result).name()); return result; } From ca9a16e30e0570d6603cab8255ce77c451a5cb7d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 00:47:28 +0200 Subject: [PATCH 141/250] Partially updated schema validation to draft v6 --- lib/json/JsonValidator.cpp | 61 ++++++++++++++++++++++---------------- lib/json/JsonValidator.h | 2 +- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/lib/json/JsonValidator.cpp b/lib/json/JsonValidator.cpp index db1ee2196..a894282e8 100644 --- a/lib/json/JsonValidator.cpp +++ b/lib/json/JsonValidator.cpp @@ -21,13 +21,12 @@ VCMI_LIB_NAMESPACE_BEGIN -//TODO: integer support - static const std::unordered_map stringToType = { {"null", JsonNode::JsonType::DATA_NULL}, {"boolean", JsonNode::JsonType::DATA_BOOL}, {"number", JsonNode::JsonType::DATA_FLOAT}, + {"integer", JsonNode::JsonType::DATA_INTEGER}, {"string", JsonNode::JsonType::DATA_STRING}, {"array", JsonNode::JsonType::DATA_VECTOR}, {"object", JsonNode::JsonType::DATA_STRUCT} @@ -132,8 +131,8 @@ namespace JsonNode::JsonType type = it->second; - //FIXME: hack for integer values - if(data.isNumber() && type == JsonNode::JsonType::DATA_FLOAT) + // for "number" type both float and integer are allowed + if(type == JsonNode::JsonType::DATA_FLOAT && data.isNumber()) return ""; if(type != data.getType() && data.getType() != JsonNode::JsonType::DATA_NULL) @@ -201,31 +200,29 @@ namespace std::string maximumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) { - if (baseSchema["exclusiveMaximum"].Bool()) - { - if (data.Float() >= schema.Float()) - return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str()); - } - else - { - if (data.Float() > schema.Float()) - return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str()); - } + if (data.Float() > schema.Float()) + return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str()); return ""; } std::string minimumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) { - if (baseSchema["exclusiveMinimum"].Bool()) - { - if (data.Float() <= schema.Float()) - return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str()); - } - else - { - if (data.Float() < schema.Float()) - return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str()); - } + if (data.Float() < schema.Float()) + return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str()); + return ""; + } + + std::string exclusiveMaximumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + if (data.Float() >= schema.Float()) + return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str()); + return ""; + } + + std::string exclusiveMinimumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + { + if (data.Float() <= schema.Float()) + return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str()); return ""; } @@ -536,6 +533,13 @@ namespace ret["default"] = Common::emptyCheck; ret["description"] = Common::emptyCheck; ret["definitions"] = Common::emptyCheck; + + // Not implemented + ret["propertyNames"] = Common::emptyCheck; + ret["contains"] = Common::emptyCheck; + ret["const"] = Common::emptyCheck; + ret["examples"] = Common::emptyCheck; + return ret; } @@ -556,8 +560,8 @@ namespace ret["minimum"] = Number::minimumCheck; ret["multipleOf"] = Number::multipleOfCheck; - ret["exclusiveMaximum"] = Common::emptyCheck; - ret["exclusiveMinimum"] = Common::emptyCheck; + ret["exclusiveMaximum"] = Number::exclusiveMaximumCheck; + ret["exclusiveMinimum"] = Number::exclusiveMinimumCheck; return ret; } @@ -598,6 +602,11 @@ namespace ret["imageFile"] = Formats::imageFile; ret["videoFile"] = Formats::videoFile; + //TODO: + // uri-reference + // uri-template + // json-pointer + return ret; } } diff --git a/lib/json/JsonValidator.h b/lib/json/JsonValidator.h index 35cc23797..5e535daa3 100644 --- a/lib/json/JsonValidator.h +++ b/lib/json/JsonValidator.h @@ -14,7 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN -//Internal class for Json validation. Mostly compilant with json-schema v4 draft +//Internal class for Json validation. Mostly compilant with json-schema v6 draft namespace Validation { /// struct used to pass data around during validation From 25146bfa93d139f40fd3e339f76da7a6b9e6e8bb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 01:00:54 +0200 Subject: [PATCH 142/250] Replace custom class with string_view --- lib/json/JsonParser.h | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/lib/json/JsonParser.h b/lib/json/JsonParser.h index 7d975927d..5ab544e41 100644 --- a/lib/json/JsonParser.h +++ b/lib/json/JsonParser.h @@ -13,37 +13,11 @@ VCMI_LIB_NAMESPACE_BEGIN -//Tiny string class that uses const char* as data for speed, members are private -//for ease of debugging and some compatibility with std::string -class constString -{ - const char *data; - const size_t datasize; - -public: - constString(const char * inputString, size_t stringSize): - data(inputString), - datasize(stringSize) - { - } - - inline size_t size() const - { - return datasize; - }; - - inline const char& operator[] (size_t position) - { - assert (position < datasize); - return data[position]; - } -}; - //Internal class for string -> JsonNode conversion class JsonParser { std::string errors; // Contains description of all encountered errors - constString input; // Input data + std::string_view input; // Input data ui32 lineCount; // Currently parsed line, starting from 1 size_t lineStart; // Position of current line start size_t pos; // Current position of parser From a2b8eaf7fbffc84ff13ab63558d3729da3c6ce1b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 01:01:13 +0200 Subject: [PATCH 143/250] Do not escape '/' when writing json --- lib/json/JsonWriter.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/json/JsonWriter.cpp b/lib/json/JsonWriter.cpp index 205343d79..2a4092b65 100644 --- a/lib/json/JsonWriter.cpp +++ b/lib/json/JsonWriter.cpp @@ -63,9 +63,8 @@ void JsonWriter::writeEntry(JsonVector::const_iterator entry) void JsonWriter::writeString(const std::string &string) { - static const std::string escaped = "\"\\\b\f\n\r\t/"; - - static const std::array escaped_code = {'\"', '\\', 'b', 'f', 'n', 'r', 't', '/'}; + static const std::string escaped = "\"\\\b\f\n\r\t"; + static const std::array escaped_code = {'\"', '\\', 'b', 'f', 'n', 'r', 't'}; out <<'\"'; size_t pos = 0; From 54796c7c56754cd72fcba955fefe088d0e18113c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 12 Feb 2024 01:22:16 +0200 Subject: [PATCH 144/250] Rename toJson to toString/toCompactString for consistency --- client/CMT.cpp | 2 +- client/ClientCommandManager.cpp | 4 ++-- client/globalLobby/GlobalLobbyClient.cpp | 4 ++-- launcher/jsonutils.cpp | 2 +- lib/CConfigHandler.cpp | 2 +- lib/CTownHandler.cpp | 2 +- lib/bonuses/Bonus.cpp | 2 +- lib/json/JsonBonus.cpp | 8 ++++---- lib/json/JsonNode.cpp | 16 ++++++++++++---- lib/json/JsonNode.h | 5 +++-- lib/json/JsonRandom.h | 4 ++-- lib/json/JsonUtils.cpp | 2 +- lib/mapping/MapFormatJson.cpp | 4 ++-- lib/modding/CModHandler.cpp | 2 +- lib/spells/CSpellHandler.cpp | 2 +- mapeditor/jsonutils.cpp | 2 +- mapeditor/mainwindow.cpp | 2 +- scripting/lua/LuaSpellEffect.cpp | 4 ++-- server/GlobalLobbyProcessor.cpp | 6 +++--- test/JsonComparer.cpp | 2 +- test/map/CMapFormatTest.cpp | 2 +- test/mock/mock_MapService.cpp | 2 +- 22 files changed, 45 insertions(+), 36 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index 5007e8345..e250a190e 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -243,7 +243,7 @@ int main(int argc, char * argv[]) // Initialize logging based on settings logConfig->configure(); - logGlobal->debug("settings = %s", settings.toJsonNode().toJson()); + logGlobal->debug("settings = %s", settings.toJsonNode().toString()); // Some basic data validation to produce better error messages in cases of incorrect install auto testFile = [](std::string filename, std::string message) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index ed5eedd5b..09ea67004 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -254,7 +254,7 @@ void ClientCommandManager::handleGetConfigCommand() const boost::filesystem::path filePath = contentOutPath / (name + ".json"); std::ofstream file(filePath.c_str()); - file << object.toJson(); + file << object.toString(); } } } @@ -358,7 +358,7 @@ void ClientCommandManager::handleBonusesCommand(std::istringstream & singleWordB auto format = [outputFormat](const BonusList & b) -> std::string { if(outputFormat == "json") - return b.toJsonNode().toJson(true); + return b.toJsonNode().toCompactString(); std::ostringstream ss; ss << b; diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index f529602ee..b5e398b9a 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -273,7 +273,7 @@ void GlobalLobbyClient::onDisconnected(const std::shared_ptr void GlobalLobbyClient::sendMessage(const JsonNode & data) { - networkConnection->sendPacket(data.toBytes(true)); + networkConnection->sendPacket(data.toBytes()); } void GlobalLobbyClient::sendOpenPublicRoom() @@ -362,5 +362,5 @@ void GlobalLobbyClient::sendProxyConnectionLogin(const NetworkConnectionPtr & ne toSend["accountCookie"] = settings["lobby"]["accountCookie"]; toSend["gameRoomID"] = settings["lobby"]["roomID"]; - netConnection->sendPacket(toSend.toBytes(true)); + netConnection->sendPacket(toSend.toBytes()); } diff --git a/launcher/jsonutils.cpp b/launcher/jsonutils.cpp index 0f472f096..af4ce0442 100644 --- a/launcher/jsonutils.cpp +++ b/launcher/jsonutils.cpp @@ -116,7 +116,7 @@ JsonNode toJson(QVariant object) void JsonToFile(QString filename, QVariant object) { std::fstream file(qstringToPath(filename).c_str(), std::ios::out | std::ios_base::binary); - file << toJson(object).toJson(); + file << toJson(object).toString(); } } diff --git a/lib/CConfigHandler.cpp b/lib/CConfigHandler.cpp index 5b7924edc..c89c125ee 100644 --- a/lib/CConfigHandler.cpp +++ b/lib/CConfigHandler.cpp @@ -90,7 +90,7 @@ void SettingsStorage::invalidateNode(const std::vector &changedPath JsonUtils::minimize(savedConf, schema); std::fstream file(CResourceHandler::get()->getResourceName(JsonPath::builtin(dataFilename))->c_str(), std::ofstream::out | std::ofstream::trunc); - file << savedConf.toJson(); + file << savedConf.toString(); } JsonNode & SettingsStorage::getNode(const std::vector & path) diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 2544c4a9c..40be76616 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -1201,7 +1201,7 @@ void CTownHandler::initializeRequirements() { logMod->error("Unexpected length of town buildings requirements: %d", node.Vector().size()); logMod->error("Entry contains: "); - logMod->error(node.toJson()); + logMod->error(node.toString()); } auto index = VLC->identifiers()->getIdentifier(requirement.town->getBuildingScope(), node[0]); diff --git a/lib/bonuses/Bonus.cpp b/lib/bonuses/Bonus.cpp index 6f7a04d3e..ac3d534a7 100644 --- a/lib/bonuses/Bonus.cpp +++ b/lib/bonuses/Bonus.cpp @@ -72,7 +72,7 @@ si32 CAddInfo::operator[](size_type pos) const std::string CAddInfo::toString() const { - return toJsonNode().toJson(true); + return toJsonNode().toCompactString(); } JsonNode CAddInfo::toJsonNode() const diff --git a/lib/json/JsonBonus.cpp b/lib/json/JsonBonus.cpp index ffc540ff8..75437c8bb 100644 --- a/lib/json/JsonBonus.cpp +++ b/lib/json/JsonBonus.cpp @@ -553,7 +553,7 @@ std::shared_ptr JsonUtils::parseBonus(const JsonNode &ability) if (!parseBonus(ability, b.get())) { // caller code can not handle this case and presumes that returned bonus is always valid - logGlobal->error("Failed to parse bonus! Json config was %S ", ability.toJson()); + logGlobal->error("Failed to parse bonus! Json config was %S ", ability.toString()); b->type = BonusType::NONE; return b; } @@ -577,7 +577,7 @@ static BonusParams convertDeprecatedBonus(const JsonNode &ability) { if(vstd::contains(deprecatedBonusSet, ability["type"].String())) { - logMod->warn("There is deprecated bonus found:\n%s\nTrying to convert...", ability.toJson()); + logMod->warn("There is deprecated bonus found:\n%s\nTrying to convert...", ability.toString()); auto params = BonusParams(ability["type"].String(), ability["subtype"].isString() ? ability["subtype"].String() : "", ability["subtype"].isNumber() ? ability["subtype"].Integer() : -1); @@ -589,11 +589,11 @@ static BonusParams convertDeprecatedBonus(const JsonNode &ability) params.targetType = BonusSource::SECONDARY_SKILL; } - logMod->warn("Please, use this bonus:\n%s\nConverted successfully!", params.toJson().toJson()); + logMod->warn("Please, use this bonus:\n%s\nConverted successfully!", params.toJson().toString()); return params; } else - logMod->error("Cannot convert bonus!\n%s", ability.toJson()); + logMod->error("Cannot convert bonus!\n%s", ability.toString()); } BonusParams ret; ret.isConverted = false; diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index 4c70d1ca3..612eed572 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -410,19 +410,27 @@ JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) return ::resolvePointer(*this, jsonPointer); } -std::vector JsonNode::toBytes(bool compact) const +std::vector JsonNode::toBytes() const { - std::string jsonString = toJson(compact); + std::string jsonString = toString(); 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::string JsonNode::toCompactString() const { std::ostringstream out; - JsonWriter writer(out, compact); + JsonWriter writer(out, true); + writer.writeNode(*this); + return out.str(); +} + +std::string JsonNode::toString() const +{ + std::ostringstream out; + JsonWriter writer(out, false); writer.writeNode(*this); return out.str(); } diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index ab4fc37f5..138fbb918 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -116,8 +116,9 @@ public: JsonNode & operator[](size_t child); const JsonNode & operator[](size_t child) const; - std::string toJson(bool compact = false) const; - std::vector toBytes(bool compact = false) const; + std::string toCompactString() const; + std::string toString() const; + std::vector toBytes() const; template void serialize(Handler &h) { diff --git a/lib/json/JsonRandom.h b/lib/json/JsonRandom.h index 79ab292ce..46443aa9a 100644 --- a/lib/json/JsonRandom.h +++ b/lib/json/JsonRandom.h @@ -23,7 +23,7 @@ struct Bonus; struct Component; class CStackBasicDescriptor; -class DLL_LINKAGE JsonRandom : public GameCallbackHolder +class JsonRandom : public GameCallbackHolder { public: using Variables = std::map; @@ -46,7 +46,7 @@ private: public: using GameCallbackHolder::GameCallbackHolder; - struct DLL_LINKAGE RandomStackInfo + struct RandomStackInfo { std::vector allowedCreatures; si32 minAmount; diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 7597c37b8..42826cf8c 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -134,7 +134,7 @@ bool JsonUtils::validate(const JsonNode & node, const std::string & schemaName, { logMod->warn("Data in %s is invalid!", dataName); logMod->warn(log); - logMod->trace("%s json: %s", dataName, node.toJson(true)); + logMod->trace("%s json: %s", dataName, node.toCompactString()); } return log.empty(); } diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 9fa0a48dd..9046234c1 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -1049,7 +1049,7 @@ void CMapLoaderJson::MapObjectLoader::construct() if(typeName.empty()) { logGlobal->error("Object type missing"); - logGlobal->debug(configuration.toJson()); + logGlobal->debug(configuration.toString()); return; } @@ -1069,7 +1069,7 @@ void CMapLoaderJson::MapObjectLoader::construct() else if(subtypeName.empty()) { logGlobal->error("Object subtype missing"); - logGlobal->debug(configuration.toJson()); + logGlobal->debug(configuration.toString()); return; } diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index ef08b5009..7356dc2a3 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -512,7 +512,7 @@ void CModHandler::afterLoad(bool onlyEssential) if(!onlyEssential) { std::fstream file(CResourceHandler::get()->getResourceName(ResourcePath("config/modSettings.json"))->c_str(), std::ofstream::out | std::ofstream::trunc); - file << modSettings.toJson(); + file << modSettings.toString(); } } diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 0aa239f93..df92f869a 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -833,7 +833,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & { logMod->warn("Spell %s has old target condition format. Expected configuration: ", spell->getNameTranslated()); spell->targetCondition = spell->convertTargetCondition(immunities, absoluteImmunities, limiters, absoluteLimiters); - logMod->warn("\n\"targetCondition\" : %s", spell->targetCondition.toJson()); + logMod->warn("\n\"targetCondition\" : %s", spell->targetCondition.toString()); } } else diff --git a/mapeditor/jsonutils.cpp b/mapeditor/jsonutils.cpp index 15649d97f..4e1a4e58c 100644 --- a/mapeditor/jsonutils.cpp +++ b/mapeditor/jsonutils.cpp @@ -122,7 +122,7 @@ JsonNode toJson(QVariant object) void JsonToFile(QString filename, QVariant object) { std::fstream file(qstringToPath(filename).c_str(), std::ios::out | std::ios_base::binary); - file << toJson(object).toJson(); + file << toJson(object).toString(); } } diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index bc4e609c7..b50353c0d 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -181,7 +181,7 @@ MainWindow::MainWindow(QWidget* parent) : // Initialize logging based on settings logConfig->configure(); - logGlobal->debug("settings = %s", settings.toJsonNode().toJson()); + logGlobal->debug("settings = %s", settings.toJsonNode().toString()); // Some basic data validation to produce better error messages in cases of incorrect install auto testFile = [](std::string filename, std::string message) -> bool diff --git a/scripting/lua/LuaSpellEffect.cpp b/scripting/lua/LuaSpellEffect.cpp index 2d51dd281..484235af6 100644 --- a/scripting/lua/LuaSpellEffect.cpp +++ b/scripting/lua/LuaSpellEffect.cpp @@ -76,7 +76,7 @@ bool LuaSpellEffect::applicable(Problem & problem, const Mechanics * m) const if(response.getType() != JsonNode::JsonType::DATA_BOOL) { logMod->error("Invalid API response from script %s.", script->getName()); - logMod->debug(response.toJson(true)); + logMod->debug(response.toCompactString()); return false; } return response.Bool(); @@ -116,7 +116,7 @@ bool LuaSpellEffect::applicable(Problem & problem, const Mechanics * m, const Ef if(response.getType() != JsonNode::JsonType::DATA_BOOL) { logMod->error("Invalid API response from script %s.", script->getName()); - logMod->debug(response.toJson(true)); + logMod->debug(response.toCompactString()); return false; } return response.Bool(); diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index df42d9fb4..f06d7c19c 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -45,7 +45,7 @@ void GlobalLobbyProcessor::onDisconnected(const std::shared_ptrsendPacket(message.toBytes(true)); + controlConnection->sendPacket(message.toBytes()); break; } } @@ -122,7 +122,7 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptrsendPacket(toSend.toBytes(true)); + connection->sendPacket(toSend.toBytes()); } else { @@ -137,7 +137,7 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptrsendPacket(toSend.toBytes(true)); + connection->sendPacket(toSend.toBytes()); proxyConnections[guestAccountID] = connection; owner.onNewConnection(connection); diff --git a/test/JsonComparer.cpp b/test/JsonComparer.cpp index 46a9b00da..1a28071cd 100644 --- a/test/JsonComparer.cpp +++ b/test/JsonComparer.cpp @@ -165,7 +165,7 @@ void JsonComparer::checkEqualJson(const JsonNode & actual, const JsonNode & expe } else { - check(false, "type mismatch. \n expected:\n"+expected.toJson(true)+"\n actual:\n" +actual.toJson(true)); + check(false, "type mismatch. \n expected:\n"+expected.toCompactString()+"\n actual:\n" +actual.toCompactString()); } } diff --git a/test/map/CMapFormatTest.cpp b/test/map/CMapFormatTest.cpp index afd72b8c3..6d6cd9e54 100644 --- a/test/map/CMapFormatTest.cpp +++ b/test/map/CMapFormatTest.cpp @@ -102,7 +102,7 @@ static JsonNode getFromArchive(CZipLoader & archive, const std::string & archive static void addToArchive(CZipSaver & saver, const JsonNode & data, const std::string & filename) { - auto s = data.toJson(); + auto s = data.toString(); std::unique_ptr stream = saver.addFile(filename); if(stream->write((const ui8*)s.c_str(), s.size()) != s.size()) diff --git a/test/mock/mock_MapService.cpp b/test/mock/mock_MapService.cpp index 6b44d634a..375ba2c96 100644 --- a/test/mock/mock_MapService.cpp +++ b/test/mock/mock_MapService.cpp @@ -84,7 +84,7 @@ void MapServiceMock::saveMap(const std::unique_ptr & map, boost::filesyste void MapServiceMock::addToArchive(CZipSaver & saver, const JsonNode & data, const std::string & filename) { - auto s = data.toJson(); + auto s = data.toString(); std::unique_ptr stream = saver.addFile(filename); if(stream->write((const ui8*)s.c_str(), s.size()) != s.size()) From 08a27663f9db1bdd102181b26fdada019387b3e1 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 13 Feb 2024 13:18:10 +0200 Subject: [PATCH 145/250] Reworked JsonNode constructors to more logical form --- client/render/CAnimation.cpp | 8 ++--- client/render/Graphics.cpp | 2 +- launcher/jsonutils.cpp | 2 +- launcher/updatedialog_moc.cpp | 2 +- lib/CSkillHandler.cpp | 2 +- lib/bonuses/Bonus.cpp | 11 +++---- lib/bonuses/BonusEnum.cpp | 6 ++-- lib/bonuses/BonusList.cpp | 2 +- lib/bonuses/Limiters.cpp | 42 +++++++++++++------------- lib/bonuses/Updaters.cpp | 26 ++++++++-------- lib/campaign/CampaignHandler.cpp | 2 +- lib/filesystem/Filesystem.cpp | 4 +-- lib/json/JsonNode.cpp | 35 +++++++++++++++------ lib/json/JsonNode.h | 24 ++++++++++----- lib/json/JsonUtils.cpp | 34 ++------------------- lib/json/JsonUtils.h | 6 ---- lib/mapObjects/MiscObjects.cpp | 9 ++---- lib/mapObjects/ObjectTemplate.cpp | 12 ++------ lib/mapping/MapFormatJson.cpp | 19 +++++------- lib/serializer/JsonSerializer.cpp | 18 ++--------- lib/spells/CSpellHandler.cpp | 2 +- lobby/LobbyServer.cpp | 2 +- mapeditor/Animation.cpp | 8 ++--- mapeditor/jsonutils.cpp | 2 +- mapeditor/mapsettings/translations.cpp | 6 ++-- 25 files changed, 125 insertions(+), 161 deletions(-) diff --git a/client/render/CAnimation.cpp b/client/render/CAnimation.cpp index 91ca64c0f..93549f333 100644 --- a/client/render/CAnimation.cpp +++ b/client/render/CAnimation.cpp @@ -102,7 +102,7 @@ void CAnimation::initFromJson(const JsonNode & config) std::string basepath; basepath = config["basepath"].String(); - JsonNode base(JsonNode::JsonType::DATA_STRUCT); + JsonNode base; base["margins"] = config["margins"]; base["width"] = config["width"]; base["height"] = config["height"]; @@ -114,7 +114,7 @@ void CAnimation::initFromJson(const JsonNode & config) for(const JsonNode & frame : group["frames"].Vector()) { - JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT); + JsonNode toAdd; JsonUtils::inherit(toAdd, base); toAdd["file"].String() = basepath + frame.String(); source[groupID].push_back(toAdd); @@ -129,7 +129,7 @@ void CAnimation::initFromJson(const JsonNode & config) if (source[group].size() <= frame) source[group].resize(frame+1); - JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT); + JsonNode toAdd; JsonUtils::inherit(toAdd, base); toAdd["file"].String() = basepath + node["file"].String(); source[group][frame] = toAdd; @@ -191,7 +191,7 @@ void CAnimation::init() std::unique_ptr textData(new ui8[stream->getSize()]); stream->read(textData.get(), stream->getSize()); - const JsonNode config((char*)textData.get(), stream->getSize()); + const JsonNode config(reinterpret_cast(textData.get()), stream->getSize()); initFromJson(config); } diff --git a/client/render/Graphics.cpp b/client/render/Graphics.cpp index 023a6ce55..ad8f90d92 100644 --- a/client/render/Graphics.cpp +++ b/client/render/Graphics.cpp @@ -107,7 +107,7 @@ void Graphics::initializeBattleGraphics() if(!CResourceHandler::get(mod)->existsResource(ResourcePath("config/battles_graphics.json"))) continue; - const JsonNode config(mod, JsonPath::builtin("config/battles_graphics.json")); + const JsonNode config(JsonPath::builtin("config/battles_graphics.json"), mod); //initialization of AC->def name mapping if(!config["ac_mapping"].isNull()) diff --git a/launcher/jsonutils.cpp b/launcher/jsonutils.cpp index af4ce0442..4635e9f50 100644 --- a/launcher/jsonutils.cpp +++ b/launcher/jsonutils.cpp @@ -89,7 +89,7 @@ QVariant JsonFromFile(QString filename) } const auto data = file.readAll(); - JsonNode node(data.data(), data.size()); + JsonNode node(reinterpret_cast(data.data()), data.size()); return toVariant(node); } diff --git a/launcher/updatedialog_moc.cpp b/launcher/updatedialog_moc.cpp index 5542fa903..55f4eb94f 100644 --- a/launcher/updatedialog_moc.cpp +++ b/launcher/updatedialog_moc.cpp @@ -67,7 +67,7 @@ UpdateDialog::UpdateDialog(bool calledManually, QWidget *parent): } auto byteArray = response->readAll(); - JsonNode node(byteArray.constData(), byteArray.size()); + JsonNode node(reinterpret_cast(byteArray.constData()), byteArray.size()); loadFromJson(node); }); } diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index 3c8a253e8..619429dc7 100644 --- a/lib/CSkillHandler.cpp +++ b/lib/CSkillHandler.cpp @@ -168,7 +168,7 @@ std::vector CSkillHandler::loadLegacyData() std::vector legacyData; for(int id = 0; id < GameConstants::SKILL_QUANTITY; id++) { - JsonNode skillNode(JsonNode::JsonType::DATA_STRUCT); + JsonNode skillNode; skillNode["name"].String() = skillNames[id]; for(int level = 1; level < NSecondarySkill::levels.size(); level++) { diff --git a/lib/bonuses/Bonus.cpp b/lib/bonuses/Bonus.cpp index ac3d534a7..35bc72c72 100644 --- a/lib/bonuses/Bonus.cpp +++ b/lib/bonuses/Bonus.cpp @@ -26,7 +26,6 @@ #include "../TerrainHandler.h" #include "../constants/StringConstants.h" #include "../battle/BattleInfo.h" -#include "../json/JsonUtils.h" #include "../modding/ModUtility.h" VCMI_LIB_NAMESPACE_BEGIN @@ -79,13 +78,13 @@ JsonNode CAddInfo::toJsonNode() const { if(size() < 2) { - return JsonUtils::intNode(operator[](0)); + return JsonNode(operator[](0)); } else { - JsonNode node(JsonNode::JsonType::DATA_VECTOR); + JsonNode node; for(si32 value : *this) - node.Vector().push_back(JsonUtils::intNode(value)); + node.Vector().emplace_back(value); return node; } } @@ -143,7 +142,7 @@ static JsonNode additionalInfoToJson(BonusType type, CAddInfo addInfo) switch(type) { case BonusType::SPECIAL_UPGRADE: - return JsonUtils::stringNode(ModUtility::makeFullIdentifier("", "creature", CreatureID::encode(addInfo[0]))); + return JsonNode(ModUtility::makeFullIdentifier("", "creature", CreatureID::encode(addInfo[0]))); default: return addInfo.toJsonNode(); } @@ -151,7 +150,7 @@ static JsonNode additionalInfoToJson(BonusType type, CAddInfo addInfo) JsonNode Bonus::toJsonNode() const { - JsonNode root(JsonNode::JsonType::DATA_STRUCT); + JsonNode root; // only add values that might reasonably be found in config files root["type"].String() = vstd::findKey(bonusNameMap, type); if(subtype != BonusSubtypeID()) diff --git a/lib/bonuses/BonusEnum.cpp b/lib/bonuses/BonusEnum.cpp index ed5fe7c59..e48d15dcf 100644 --- a/lib/bonuses/BonusEnum.cpp +++ b/lib/bonuses/BonusEnum.cpp @@ -67,13 +67,13 @@ namespace BonusDuration } if(durationNames.size() == 1) { - return JsonUtils::stringNode(durationNames[0]); + return JsonNode(durationNames[0]); } else { - JsonNode node(JsonNode::JsonType::DATA_VECTOR); + JsonNode node; for(const std::string & dur : durationNames) - node.Vector().push_back(JsonUtils::stringNode(dur)); + node.Vector().emplace_back(dur); return node; } } diff --git a/lib/bonuses/BonusList.cpp b/lib/bonuses/BonusList.cpp index 3fbabefe3..752335044 100644 --- a/lib/bonuses/BonusList.cpp +++ b/lib/bonuses/BonusList.cpp @@ -213,7 +213,7 @@ int BonusList::valOfBonuses(const CSelector &select) const JsonNode BonusList::toJsonNode() const { - JsonNode node(JsonNode::JsonType::DATA_VECTOR); + JsonNode node; for(const std::shared_ptr & b : bonuses) node.Vector().push_back(b->toJsonNode()); return node; diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index 2e952a56f..e17bf3965 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -92,7 +92,7 @@ std::string ILimiter::toString() const JsonNode ILimiter::toJsonNode() const { - JsonNode root(JsonNode::JsonType::DATA_STRUCT); + JsonNode root; root["type"].String() = toString(); return root; } @@ -127,11 +127,11 @@ std::string CCreatureTypeLimiter::toString() const JsonNode CCreatureTypeLimiter::toJsonNode() const { - JsonNode root(JsonNode::JsonType::DATA_STRUCT); + JsonNode root; root["type"].String() = "CREATURE_TYPE_LIMITER"; - root["parameters"].Vector().push_back(JsonUtils::stringNode(creature->getJsonKey())); - root["parameters"].Vector().push_back(JsonUtils::boolNode(includeUpgrades)); + root["parameters"].Vector().emplace_back(creature->getJsonKey()); + root["parameters"].Vector().emplace_back(includeUpgrades); return root; } @@ -199,16 +199,16 @@ std::string HasAnotherBonusLimiter::toString() const JsonNode HasAnotherBonusLimiter::toJsonNode() const { - JsonNode root(JsonNode::JsonType::DATA_STRUCT); + JsonNode root; std::string typeName = vstd::findKey(bonusNameMap, type); auto sourceTypeName = vstd::findKey(bonusSourceMap, source); root["type"].String() = "HAS_ANOTHER_BONUS_LIMITER"; - root["parameters"].Vector().push_back(JsonUtils::stringNode(typeName)); + root["parameters"].Vector().emplace_back(typeName); if(isSubtypeRelevant) - root["parameters"].Vector().push_back(JsonUtils::stringNode(subtype.toString())); + root["parameters"].Vector().emplace_back(subtype.toString()); if(isSourceRelevant) - root["parameters"].Vector().push_back(JsonUtils::stringNode(sourceTypeName)); + root["parameters"].Vector().emplace_back(sourceTypeName); return root; } @@ -233,11 +233,11 @@ UnitOnHexLimiter::UnitOnHexLimiter(const std::set & applicableHexes): JsonNode UnitOnHexLimiter::toJsonNode() const { - JsonNode root(JsonNode::JsonType::DATA_STRUCT); + JsonNode root; root["type"].String() = "UNIT_ON_HEXES"; for(const auto & hex : applicableHexes) - root["parameters"].Vector().push_back(JsonUtils::intNode(hex)); + root["parameters"].Vector().emplace_back(hex); return root; } @@ -278,11 +278,11 @@ std::string CreatureTerrainLimiter::toString() const JsonNode CreatureTerrainLimiter::toJsonNode() const { - JsonNode root(JsonNode::JsonType::DATA_STRUCT); + JsonNode root; root["type"].String() = "CREATURE_TERRAIN_LIMITER"; auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey(); - root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainName)); + root["parameters"].Vector().emplace_back(terrainName); return root; } @@ -324,10 +324,10 @@ std::string FactionLimiter::toString() const JsonNode FactionLimiter::toJsonNode() const { - JsonNode root(JsonNode::JsonType::DATA_STRUCT); + JsonNode root; root["type"].String() = "FACTION_LIMITER"; - root["parameters"].Vector().push_back(JsonUtils::stringNode(VLC->factions()->getById(faction)->getJsonKey())); + root["parameters"].Vector().emplace_back(VLC->factions()->getById(faction)->getJsonKey()); return root; } @@ -354,11 +354,11 @@ std::string CreatureLevelLimiter::toString() const JsonNode CreatureLevelLimiter::toJsonNode() const { - JsonNode root(JsonNode::JsonType::DATA_STRUCT); + JsonNode root; root["type"].String() = "CREATURE_LEVEL_LIMITER"; - root["parameters"].Vector().push_back(JsonUtils::intNode(minLevel)); - root["parameters"].Vector().push_back(JsonUtils::intNode(maxLevel)); + root["parameters"].Vector().emplace_back(minLevel); + root["parameters"].Vector().emplace_back(maxLevel); return root; } @@ -392,10 +392,10 @@ std::string CreatureAlignmentLimiter::toString() const JsonNode CreatureAlignmentLimiter::toJsonNode() const { - JsonNode root(JsonNode::JsonType::DATA_STRUCT); + JsonNode root; root["type"].String() = "CREATURE_ALIGNMENT_LIMITER"; - root["parameters"].Vector().push_back(JsonUtils::stringNode(GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)])); + root["parameters"].Vector().emplace_back(GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)]); return root; } @@ -450,8 +450,8 @@ void AggregateLimiter::add(const TLimiterPtr & limiter) JsonNode AggregateLimiter::toJsonNode() const { - JsonNode result(JsonNode::JsonType::DATA_VECTOR); - result.Vector().push_back(JsonUtils::stringNode(getAggregator())); + JsonNode result; + result.Vector().emplace_back(getAggregator()); for(const auto & l : limiters) result.Vector().push_back(l->toJsonNode()); return result; diff --git a/lib/bonuses/Updaters.cpp b/lib/bonuses/Updaters.cpp index 5729574c0..5a41aa047 100644 --- a/lib/bonuses/Updaters.cpp +++ b/lib/bonuses/Updaters.cpp @@ -13,7 +13,7 @@ #include "Updaters.h" #include "Limiters.h" -#include "../json/JsonUtils.h" +#include "../json/JsonNode.h" #include "../mapObjects/CGHeroInstance.h" #include "../CStack.h" @@ -39,7 +39,7 @@ std::string IUpdater::toString() const JsonNode IUpdater::toJsonNode() const { - return JsonNode(JsonNode::JsonType::DATA_NULL); + return JsonNode(); } GrowsWithLevelUpdater::GrowsWithLevelUpdater(int valPer20, int stepSize) : valPer20(valPer20), stepSize(stepSize) @@ -69,12 +69,12 @@ std::string GrowsWithLevelUpdater::toString() const JsonNode GrowsWithLevelUpdater::toJsonNode() const { - JsonNode root(JsonNode::JsonType::DATA_STRUCT); + JsonNode root; root["type"].String() = "GROWS_WITH_LEVEL"; - root["parameters"].Vector().push_back(JsonUtils::intNode(valPer20)); + root["parameters"].Vector().emplace_back(valPer20); if(stepSize > 1) - root["parameters"].Vector().push_back(JsonUtils::intNode(stepSize)); + root["parameters"].Vector().emplace_back(stepSize); return root; } @@ -98,7 +98,7 @@ std::string TimesHeroLevelUpdater::toString() const JsonNode TimesHeroLevelUpdater::toJsonNode() const { - return JsonUtils::stringNode("TIMES_HERO_LEVEL"); + return JsonNode("TIMES_HERO_LEVEL"); } ArmyMovementUpdater::ArmyMovementUpdater(): @@ -141,13 +141,13 @@ std::string ArmyMovementUpdater::toString() const JsonNode ArmyMovementUpdater::toJsonNode() const { - JsonNode root(JsonNode::JsonType::DATA_STRUCT); + JsonNode root; root["type"].String() = "ARMY_MOVEMENT"; - root["parameters"].Vector().push_back(JsonUtils::intNode(base)); - root["parameters"].Vector().push_back(JsonUtils::intNode(divider)); - root["parameters"].Vector().push_back(JsonUtils::intNode(multiplier)); - root["parameters"].Vector().push_back(JsonUtils::intNode(max)); + root["parameters"].Vector().emplace_back(base); + root["parameters"].Vector().emplace_back(divider); + root["parameters"].Vector().emplace_back(multiplier); + root["parameters"].Vector().emplace_back(max); return root; } @@ -183,7 +183,7 @@ std::string TimesStackLevelUpdater::toString() const JsonNode TimesStackLevelUpdater::toJsonNode() const { - return JsonUtils::stringNode("TIMES_STACK_LEVEL"); + return JsonNode("TIMES_STACK_LEVEL"); } std::string OwnerUpdater::toString() const @@ -193,7 +193,7 @@ std::string OwnerUpdater::toString() const JsonNode OwnerUpdater::toJsonNode() const { - return JsonUtils::stringNode("BONUS_OWNER_UPDATER"); + return JsonNode("BONUS_OWNER_UPDATER"); } std::shared_ptr OwnerUpdater::createUpdatedBonus(const std::shared_ptr & b, const CBonusSystemNode & context) const diff --git a/lib/campaign/CampaignHandler.cpp b/lib/campaign/CampaignHandler.cpp index 5fa914573..5aed7fe91 100644 --- a/lib/campaign/CampaignHandler.cpp +++ b/lib/campaign/CampaignHandler.cpp @@ -46,7 +46,7 @@ void CampaignHandler::readCampaign(Campaign * ret, const std::vector & inpu } else // text format (json) { - JsonNode jsonCampaign((const char*)input.data(), input.size()); + JsonNode jsonCampaign(reinterpret_cast(input.data()), input.size()); readHeaderFromJson(*ret, jsonCampaign, filename, modName, encoding); for(auto & scenario : jsonCampaign["scenarios"].Vector()) diff --git a/lib/filesystem/Filesystem.cpp b/lib/filesystem/Filesystem.cpp index 1fa7e9994..7b4821c5c 100644 --- a/lib/filesystem/Filesystem.cpp +++ b/lib/filesystem/Filesystem.cpp @@ -117,7 +117,7 @@ void CFilesystemGenerator::loadJsonMap(const std::string &mountPoint, const Json if (filename) { auto configData = CResourceHandler::get("initial")->load(JsonPath::builtin(URI))->readAll(); - const JsonNode configInitial(reinterpret_cast(configData.first.get()), configData.second); + const JsonNode configInitial(reinterpret_cast(configData.first.get()), configData.second); filesystem->addLoader(new CMappedFileLoader(mountPoint, configInitial), false); } } @@ -212,7 +212,7 @@ void CResourceHandler::load(const std::string &fsConfigURI, bool extractArchives { auto fsConfigData = get("initial")->load(JsonPath::builtin(fsConfigURI))->readAll(); - const JsonNode fsConfig(reinterpret_cast(fsConfigData.first.get()), fsConfigData.second); + const JsonNode fsConfig(reinterpret_cast(fsConfigData.first.get()), fsConfigData.second); addFilesystem("data", ModScope::scopeBuiltin(), createFileSystem("", fsConfig["filesystem"], extractArchives)); } diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index 612eed572..6ed988792 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -56,18 +56,33 @@ class CModHandler; static const JsonNode nullNode; -JsonNode::JsonNode(JsonType Type) -{ - setType(Type); -} - -JsonNode::JsonNode(const std::byte *data, size_t datasize) - :JsonNode(reinterpret_cast(data), datasize) +JsonNode::JsonNode(bool boolean) + :data(boolean) {} -JsonNode::JsonNode(const char *data, size_t datasize) +JsonNode::JsonNode(int32_t number) + :data(static_cast(number)) +{} + +JsonNode::JsonNode(uint32_t number) + :data(static_cast(number)) +{} + +JsonNode::JsonNode(int64_t number) + :data(number) +{} + +JsonNode::JsonNode(double number) + :data(number) +{} + +JsonNode::JsonNode(const std::string & string) + :data(string) +{} + +JsonNode::JsonNode(const std::byte *data, size_t datasize) { - JsonParser parser(data, datasize); + JsonParser parser(reinterpret_cast(data), datasize); *this = parser.parse(""); } @@ -79,7 +94,7 @@ JsonNode::JsonNode(const JsonPath & fileURI) *this = parser.parse(fileURI.getName()); } -JsonNode::JsonNode(const std::string & idx, const JsonPath & fileURI) +JsonNode::JsonNode(const JsonPath & fileURI, const std::string & idx) { auto file = CResourceHandler::get(idx)->load(fileURI)->readAll(); diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index 138fbb918..f9a235734 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -37,24 +37,32 @@ public: }; private: - using JsonData = std::variant; + using JsonData = std::variant; JsonData data; public: /// free to use metadata fields std::string meta; - // meta-flags like override + /// meta-flags like override std::vector flags; - //Create empty node - JsonNode(JsonType Type = JsonType::DATA_NULL); - //Create tree from Json-formatted input - explicit JsonNode(const char * data, size_t datasize); + JsonNode() = default; + + /// Create single node with specified value + explicit JsonNode(bool boolean); + explicit JsonNode(int32_t number); + explicit JsonNode(uint32_t number); + explicit JsonNode(int64_t number); + explicit JsonNode(double number); + explicit JsonNode(const std::string & string); + + /// Create tree from Json-formatted input explicit JsonNode(const std::byte * data, size_t datasize); - //Create tree from JSON file + + /// Create tree from JSON file explicit JsonNode(const JsonPath & fileURI); - explicit JsonNode(const std::string & modName, const JsonPath & fileURI); + explicit JsonNode(const JsonPath & fileURI, const std::string & modName); explicit JsonNode(const JsonPath & fileURI, bool & isValidSyntax); bool operator == (const JsonNode &other) const; diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 42826cf8c..643ef1488 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -273,7 +273,7 @@ JsonNode JsonUtils::intersect(const JsonNode & a, const JsonNode & b, bool prune if(a.getType() == JsonNode::JsonType::DATA_STRUCT && b.getType() == JsonNode::JsonType::DATA_STRUCT) { // intersect individual properties - JsonNode result(JsonNode::JsonType::DATA_STRUCT); + JsonNode result; for(const auto & property : a.Struct()) { if(vstd::contains(b.Struct(), property.first)) @@ -313,7 +313,7 @@ JsonNode JsonUtils::difference(const JsonNode & node, const JsonNode & base) if(node.getType() == JsonNode::JsonType::DATA_STRUCT && base.getType() == JsonNode::JsonType::DATA_STRUCT) { // subtract individual properties - JsonNode result(JsonNode::JsonType::DATA_STRUCT); + JsonNode result; for(const auto & property : node.Struct()) { if(vstd::contains(base.Struct(), property.first)) @@ -370,38 +370,10 @@ JsonNode JsonUtils::assembleFromFiles(const std::string & filename) std::unique_ptr textData(new ui8[stream->getSize()]); stream->read(textData.get(), stream->getSize()); - JsonNode section(reinterpret_cast(textData.get()), stream->getSize()); + JsonNode section(reinterpret_cast(textData.get()), stream->getSize()); merge(result, section); } return result; } -DLL_LINKAGE JsonNode JsonUtils::boolNode(bool value) -{ - JsonNode node; - node.Bool() = value; - return node; -} - -DLL_LINKAGE JsonNode JsonUtils::floatNode(double value) -{ - JsonNode node; - node.Float() = value; - return node; -} - -DLL_LINKAGE JsonNode JsonUtils::stringNode(const std::string & value) -{ - JsonNode node; - node.String() = value; - return node; -} - -DLL_LINKAGE JsonNode JsonUtils::intNode(si64 value) -{ - JsonNode node; - node.Integer() = value; - return node; -} - VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonUtils.h b/lib/json/JsonUtils.h index ec590e1d1..33d01f888 100644 --- a/lib/json/JsonUtils.h +++ b/lib/json/JsonUtils.h @@ -88,12 +88,6 @@ namespace JsonUtils /// get schema by json URI: vcmi:# /// example: schema "vcmi:settings" is used to check user settings DLL_LINKAGE const JsonNode & getSchema(const std::string & URI); - - /// for easy construction of JsonNodes; helps with inserting primitives into vector node - DLL_LINKAGE JsonNode boolNode(bool value); - DLL_LINKAGE JsonNode floatNode(double value); - DLL_LINKAGE JsonNode stringNode(const std::string & value); - DLL_LINKAGE JsonNode intNode(si64 value); } VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index f83fbe362..943ba9356 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -220,13 +220,10 @@ void CGMine::serializeJsonOptions(JsonSerializeFormat & handler) { if(handler.saving) { - JsonNode node(JsonNode::JsonType::DATA_VECTOR); + JsonNode node; for(const auto & resID : abandonedMineResources) - { - JsonNode one(JsonNode::JsonType::DATA_STRING); - one.String() = GameConstants::RESOURCE_NAMES[resID.getNum()]; - node.Vector().push_back(one); - } + node.Vector().emplace_back(GameConstants::RESOURCE_NAMES[resID.getNum()]); + handler.serializeRaw("possibleResources", node, std::nullopt); } else diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 6fced7254..b5170bef8 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -354,11 +354,7 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const JsonVector & data = node["allowedTerrains"].Vector(); for(auto type : allowedTerrains) - { - JsonNode value(JsonNode::JsonType::DATA_STRING); - value.String() = VLC->terrainTypeHandler->getById(type)->getJsonKey(); - data.push_back(value); - } + data.push_back(JsonNode(VLC->terrainTypeHandler->getById(type)->getJsonKey())); } } @@ -398,13 +394,11 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const for(size_t i=0; i < height; i++) { - JsonNode lineNode(JsonNode::JsonType::DATA_STRING); - - std::string & line = lineNode.String(); + std::string line; line.resize(width); for(size_t j=0; j < width; j++) line[j] = tileToChar(usedTiles[height - 1 - i][width - 1 - j]); - mask.push_back(lineNode); + mask.emplace_back(line); } if(printPriority != 0) diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 9046234c1..ba7709e70 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -545,13 +545,10 @@ void CMapFormatJson::writeTeams(JsonSerializer & handler) for(const std::set & teamData : teamsData) { - JsonNode team(JsonNode::JsonType::DATA_VECTOR); + JsonNode team; for(const PlayerColor & player : teamData) - { - JsonNode member(JsonNode::JsonType::DATA_STRING); - member.String() = GameConstants::PLAYER_COLOR_NAMES[player.getNum()]; - team.Vector().push_back(std::move(member)); - } + team.Vector().emplace_back(GameConstants::PLAYER_COLOR_NAMES[player.getNum()]); + dest.Vector().push_back(std::move(team)); } handler.serializeRaw("teams", dest, std::nullopt); @@ -586,7 +583,7 @@ void CMapFormatJson::readTriggeredEvent(TriggeredEvent & event, const JsonNode & void CMapFormatJson::writeTriggeredEvents(JsonSerializer & handler) { - JsonNode triggeredEvents(JsonNode::JsonType::DATA_STRUCT); + JsonNode triggeredEvents; for(const auto & event : mapHeader->triggeredEvents) writeTriggeredEvent(event, triggeredEvents[event.identifier]); @@ -657,7 +654,7 @@ void CMapFormatJson::writeDisposedHeroes(JsonSerializeFormat & handler) auto definition = definitions->enterStruct(type); - JsonNode players(JsonNode::JsonType::DATA_VECTOR); + JsonNode players; definition->serializeIdArray("availableFor", hero.players); } } @@ -812,7 +809,7 @@ JsonNode CMapLoaderJson::getFromArchive(const std::string & archiveFilename) auto data = loader.load(resource)->readAll(); - JsonNode res(reinterpret_cast(data.first.get()), data.second); + JsonNode res(reinterpret_cast(data.first.get()), data.second); return res; } @@ -1329,7 +1326,7 @@ void CMapSaverJson::writeTerrain() void CMapSaverJson::writeObjects() { logGlobal->trace("Saving objects"); - JsonNode data(JsonNode::JsonType::DATA_STRUCT); + JsonNode data; JsonSerializer handler(mapObjectResolver.get(), data); @@ -1343,7 +1340,7 @@ void CMapSaverJson::writeObjects() if(map->grailPos.valid()) { - JsonNode grail(JsonNode::JsonType::DATA_STRUCT); + JsonNode grail; grail["type"].String() = "grail"; grail["x"].Float() = map->grailPos.x; diff --git a/lib/serializer/JsonSerializer.cpp b/lib/serializer/JsonSerializer.cpp index 05b8e34a2..b699c2ee9 100644 --- a/lib/serializer/JsonSerializer.cpp +++ b/lib/serializer/JsonSerializer.cpp @@ -60,11 +60,7 @@ void JsonSerializer::serializeInternal(const std::string & fieldName, std::vecto data.reserve(value.size()); for(const si32 rawId : value) - { - JsonNode jsonElement(JsonNode::JsonType::DATA_STRING); - jsonElement.String() = encoder(rawId); - data.push_back(std::move(jsonElement)); - } + data.emplace_back(rawId); } void JsonSerializer::serializeInternal(const std::string & fieldName, std::vector & value) @@ -76,11 +72,7 @@ void JsonSerializer::serializeInternal(const std::string & fieldName, std::vecto data.reserve(value.size()); for(const auto & rawId : value) - { - JsonNode jsonElement(JsonNode::JsonType::DATA_STRING); - jsonElement.String() = rawId; - data.push_back(std::move(jsonElement)); - } + data.emplace_back(rawId); } void JsonSerializer::serializeInternal(std::string & value) @@ -183,11 +175,7 @@ void JsonSerializer::writeLICPartBuffer(const std::string & fieldName, const std auto & target = currentObject->operator[](fieldName)[partName].Vector(); for(auto & s : buffer) - { - JsonNode val(JsonNode::JsonType::DATA_STRING); - std::swap(val.String(), s); - target.push_back(std::move(val)); - } + target.emplace_back(s); } } diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index df92f869a..97df5943e 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -582,7 +582,7 @@ std::vector CSpellHandler::loadLegacyData() { do { - JsonNode lineNode(JsonNode::JsonType::DATA_STRUCT); + JsonNode lineNode; const auto id = legacyData.size(); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 56f8bba3d..45af54a1a 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -58,7 +58,7 @@ NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) c void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json) { - target->sendPacket(json.toBytes(true)); + target->sendPacket(json.toBytes()); } void LobbyServer::sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie) diff --git a/mapeditor/Animation.cpp b/mapeditor/Animation.cpp index 5aff952da..16019a65a 100644 --- a/mapeditor/Animation.cpp +++ b/mapeditor/Animation.cpp @@ -599,7 +599,7 @@ void Animation::init() std::unique_ptr textData(new ui8[stream->getSize()]); stream->read(textData.get(), stream->getSize()); - const JsonNode config((char*)textData.get(), stream->getSize()); + const JsonNode config(reinterpret_cast(textData.get()), stream->getSize()); initFromJson(config); } @@ -610,7 +610,7 @@ void Animation::initFromJson(const JsonNode & config) std::string basepath; basepath = config["basepath"].String(); - JsonNode base(JsonNode::JsonType::DATA_STRUCT); + JsonNode base; base["margins"] = config["margins"]; base["width"] = config["width"]; base["height"] = config["height"]; @@ -622,7 +622,7 @@ void Animation::initFromJson(const JsonNode & config) for(const JsonNode & frame : group["frames"].Vector()) { - JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT); + JsonNode toAdd; JsonUtils::inherit(toAdd, base); toAdd["file"].String() = basepath + frame.String(); source[groupID].push_back(toAdd); @@ -637,7 +637,7 @@ void Animation::initFromJson(const JsonNode & config) if (source[group].size() <= frame) source[group].resize(frame+1); - JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT); + JsonNode toAdd; JsonUtils::inherit(toAdd, base); toAdd["file"].String() = basepath + node["file"].String(); source[group][frame] = toAdd; diff --git a/mapeditor/jsonutils.cpp b/mapeditor/jsonutils.cpp index 4e1a4e58c..7c8f946ac 100644 --- a/mapeditor/jsonutils.cpp +++ b/mapeditor/jsonutils.cpp @@ -96,7 +96,7 @@ QVariant JsonFromFile(QString filename) } else { - JsonNode node(data.data(), data.size()); + JsonNode node(reinterpret_cast(data.data()), data.size()); return toVariant(node); } } diff --git a/mapeditor/mapsettings/translations.cpp b/mapeditor/mapsettings/translations.cpp index ae5476a89..dd3f6e234 100644 --- a/mapeditor/mapsettings/translations.cpp +++ b/mapeditor/mapsettings/translations.cpp @@ -24,7 +24,7 @@ void Translations::cleanupRemovedItems(CMap & map) for(auto & translations : map.translations.Struct()) { - auto updateTranslations = JsonNode(JsonNode::JsonType::DATA_STRUCT); + JsonNode updateTranslations; for(auto & s : translations.second.Struct()) { for(auto part : QString::fromStdString(s.first).split('.')) @@ -44,7 +44,7 @@ void Translations::cleanupRemovedItems(CMap & map, const std::string & pattern) { for(auto & translations : map.translations.Struct()) { - auto updateTranslations = JsonNode(JsonNode::JsonType::DATA_STRUCT); + JsonNode updateTranslations; for(auto & s : translations.second.Struct()) { if(s.first.find(pattern) == std::string::npos) @@ -171,7 +171,7 @@ void Translations::on_supportedCheck_toggled(bool checked) } ui->translationsTable->blockSignals(true); ui->translationsTable->setRowCount(0); - translation = JsonNode(JsonNode::JsonType::DATA_NULL); + translation.clear(); ui->translationsTable->blockSignals(false); ui->translationsTable->setEnabled(false); } From e73516b7d12e9f32b753e3dc344e82f3f887a101 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 13 Feb 2024 13:51:24 +0200 Subject: [PATCH 146/250] Simplified template magic in JsonNode --- lib/json/JsonNode.cpp | 2 - lib/json/JsonNode.h | 137 +++++++++++++++--------------------------- 2 files changed, 48 insertions(+), 91 deletions(-) diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index 6ed988792..290e3a647 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -49,8 +49,6 @@ Node & resolvePointer(Node & in, const std::string & pointer) VCMI_LIB_NAMESPACE_BEGIN -using namespace JsonDetail; - class LibClasses; class CModHandler; diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index f9a235734..14374c09d 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -138,106 +138,65 @@ public: namespace JsonDetail { - // conversion helpers for JsonNode::convertTo (partial template function instantiation is illegal in c++) - template - struct JsonConvImpl; +inline void convert(bool & value, const JsonNode & node) +{ + value = node.Bool(); +} - template - struct JsonConvImpl +template +auto convert(T & value, const JsonNode & node) -> std::enable_if_t> +{ + value = node.Integer(); +} + +template +auto convert(T & value, const JsonNode & node) -> std::enable_if_t> +{ + value = node.Float(); +} + +inline void convert(std::string & value, const JsonNode & node) +{ + value = node.String(); +} + +template +void convert(std::map & value, const JsonNode & node) +{ + value.clear(); + for (const JsonMap::value_type & entry : node.Struct()) + value.insert(entry.first, entry.second.convertTo()); +} + +template +void convert(std::set & value, const JsonNode & node) +{ + value.clear(); + for(const JsonVector::value_type & entry : node.Vector()) { - static T convertImpl(const JsonNode & node) - { - return T((int)node.Float()); - } - }; + value.insert(entry.convertTo()); + } +} - template - struct JsonConvImpl +template +void convert(std::vector & value, const JsonNode & node) +{ + value.clear(); + for(const JsonVector::value_type & entry : node.Vector()) { - static T convertImpl(const JsonNode & node) - { - return T(node.Float()); - } - }; + value.push_back(entry.convertTo()); + } +} - template - struct JsonConverter - { - static Type convert(const JsonNode & node) - { - ///this should be triggered only for numeric types and enums - static_assert(std::is_arithmetic_v || std::is_enum_v || std::is_class_v, "Unsupported type for JsonNode::convertTo()!"); - return JsonConvImpl || std::is_class_v >::convertImpl(node); - - } - }; - - template - struct JsonConverter > - { - static std::map convert(const JsonNode & node) - { - std::map ret; - for (const JsonMap::value_type & entry : node.Struct()) - { - ret.insert(entry.first, entry.second.convertTo()); - } - return ret; - } - }; - - template - struct JsonConverter > - { - static std::set convert(const JsonNode & node) - { - std::set ret; - for(const JsonVector::value_type & entry : node.Vector()) - { - ret.insert(entry.convertTo()); - } - return ret; - } - }; - - template - struct JsonConverter > - { - static std::vector convert(const JsonNode & node) - { - std::vector ret; - for (const JsonVector::value_type & entry: node.Vector()) - { - ret.push_back(entry.convertTo()); - } - return ret; - } - }; - - template<> - struct JsonConverter - { - static std::string convert(const JsonNode & node) - { - return node.String(); - } - }; - - template<> - struct JsonConverter - { - static bool convert(const JsonNode & node) - { - return node.Bool(); - } - }; } template Type JsonNode::convertTo() const { - return JsonDetail::JsonConverter::convert(*this); + Type result; + JsonDetail::convert(result, *this); + return result; } VCMI_LIB_NAMESPACE_END From 922966dcf884172ba2189603d41a53c01ee39117 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 13 Feb 2024 14:34:16 +0200 Subject: [PATCH 147/250] Renamed JsonNode::meta to more logical modScope. Member is now private --- client/ClientCommandManager.cpp | 2 +- lib/CArtHandler.cpp | 4 +-- lib/CCreatureHandler.cpp | 6 ++-- lib/CHeroHandler.cpp | 6 ++-- lib/CTownHandler.cpp | 30 +++++++++---------- lib/bonuses/BonusParams.cpp | 2 +- lib/json/JsonBonus.cpp | 4 +-- lib/json/JsonNode.cpp | 13 +++++--- lib/json/JsonNode.h | 9 +++--- lib/json/JsonRandom.cpp | 4 +-- lib/json/JsonUtils.cpp | 2 +- lib/json/JsonValidator.cpp | 20 ++++++------- lib/json/JsonWriter.cpp | 8 ++--- .../CBankInstanceConstructor.cpp | 2 +- .../CObjectClassesHandler.cpp | 10 +++---- .../CRewardableConstructor.cpp | 2 +- .../CommonConstructors.cpp | 2 +- .../DwellingInstanceConstructor.cpp | 2 +- lib/mapping/CMapService.cpp | 2 +- lib/mapping/MapFormatH3M.cpp | 10 +++---- lib/mapping/MapIdentifiersH3M.cpp | 10 +++---- lib/modding/CModInfo.cpp | 2 +- lib/modding/ContentTypeHandler.cpp | 10 +++---- lib/modding/IdentifierStorage.cpp | 10 +++---- lib/rewardable/Info.cpp | 6 ++-- lib/serializer/JsonDeserializer.cpp | 2 +- lib/spells/CSpellHandler.cpp | 4 +-- lib/spells/TargetCondition.cpp | 2 +- scripting/lua/LuaScriptingContext.cpp | 4 +-- scripting/lua/LuaSpellEffect.cpp | 12 ++++---- scripting/lua/LuaStack.cpp | 4 +-- test/entity/CCreatureTest.cpp | 6 ++-- test/game/CGameStateTest.cpp | 2 +- test/map/CMapFormatTest.cpp | 2 +- test/scripting/LuaSpellEffectAPITest.cpp | 22 +++++++------- test/scripting/LuaSpellEffectTest.cpp | 18 +++++------ test/scripting/ScriptFixture.cpp | 2 +- test/spells/TargetConditionTest.cpp | 2 +- test/spells/effects/CatapultTest.cpp | 2 +- test/spells/effects/CloneTest.cpp | 2 +- test/spells/effects/DamageTest.cpp | 4 +-- test/spells/effects/DispelTest.cpp | 6 ++-- test/spells/effects/HealTest.cpp | 16 +++++----- test/spells/effects/SacrificeTest.cpp | 4 +-- test/spells/effects/SummonTest.cpp | 4 +-- test/spells/effects/TimedTest.cpp | 4 +-- 46 files changed, 154 insertions(+), 148 deletions(-) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 09ea67004..cf02eead0 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -248,7 +248,7 @@ void ClientCommandManager::handleGetConfigCommand() { const JsonNode& object = nameAndObject.second; - std::string name = ModUtility::makeFullIdentifier(object.meta, contentName, nameAndObject.first); + std::string name = ModUtility::makeFullIdentifier(object.getModScope(), contentName, nameAndObject.first); boost::algorithm::replace_all(name, ":", "_"); diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 6a205217a..f5bac24e5 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -461,7 +461,7 @@ CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode VLC->identifiers()->requestIdentifier(scope, "object", "artifact", [=](si32 index) { JsonNode conf; - conf.setMeta(scope); + conf.setModScope(scope); VLC->objtypeh->loadSubObject(art->identifier, conf, Obj::ARTIFACT, art->getIndex()); @@ -469,7 +469,7 @@ CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode { JsonNode templ; templ["animation"].String() = art->advMapDef; - templ.setMeta(scope); + templ.setModScope(scope); // add new template. // Necessary for objects added via mods that don't have any templates in H3 diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 36db9c134..21fda1133 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -418,7 +418,7 @@ void CCreatureHandler::loadCommanders() std::string modSource = VLC->modh->findResourceOrigin(configResource); JsonNode data(configResource); - data.setMeta(modSource); + data.setModScope(modSource); const JsonNode & config = data; // switch to const data accessors @@ -640,7 +640,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json VLC->identifiers()->requestIdentifier(scope, "object", "monster", [=](si32 index) { JsonNode conf; - conf.setMeta(scope); + conf.setModScope(scope); VLC->objtypeh->loadSubObject(cre->identifier, conf, Obj::MONSTER, cre->getId().num); if (!advMapFile.isNull()) @@ -649,7 +649,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json templ["animation"] = advMapFile; if (!advMapMask.isNull()) templ["mask"] = advMapMask; - templ.setMeta(scope); + templ.setModScope(scope); // if creature has custom advMapFile, reset any potentially imported H3M templates and use provided file instead VLC->objtypeh->getHandlerFor(Obj::MONSTER, cre->getId().num)->clearTemplates(); diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 5f96c9999..e73f9bd0b 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -293,7 +293,7 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js for(auto skillPair : node["secondarySkills"].Struct()) { int probability = static_cast(skillPair.second.Integer()); - VLC->identifiers()->requestIdentifier(skillPair.second.meta, "skill", skillPair.first, [heroClass, probability](si32 skillID) + VLC->identifiers()->requestIdentifier(skillPair.second.getModScope(), "skill", skillPair.first, [heroClass, probability](si32 skillID) { heroClass->secSkillProbability[skillID] = probability; }); @@ -310,7 +310,7 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js { int value = static_cast(tavern.second.Float()); - VLC->identifiers()->requestIdentifier(tavern.second.meta, "faction", tavern.first, + VLC->identifiers()->requestIdentifier(tavern.second.getModScope(), "faction", tavern.first, [=](si32 factionID) { heroClass->selectionProbability[FactionID(factionID)] = value; @@ -329,7 +329,7 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js classConf["heroClass"].String() = identifier; if (!node["compatibilityIdentifiers"].isNull()) classConf["compatibilityIdentifiers"] = node["compatibilityIdentifiers"]; - classConf.setMeta(scope); + classConf.setModScope(scope); VLC->objtypeh->loadSubObject(identifier, classConf, index, heroClass->getIndex()); }); diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 40be76616..5eb5b2e4b 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -617,7 +617,7 @@ void CTownHandler::loadSpecialBuildingBonuses(const JsonNode & source, BonusList void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source) { assert(stringID.find(':') == std::string::npos); - assert(!source.meta.empty()); + assert(!source.getModScope().empty()); auto * ret = new CBuilding(); ret->bid = getMappedValue(stringID, BuildingID::NONE, MappedKeys::BUILDING_NAMES_TO_TYPES, false); @@ -640,11 +640,11 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons ret->height = getMappedValue(source["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES); ret->identifier = stringID; - ret->modScope = source.meta; + ret->modScope = source.getModScope(); ret->town = town; - VLC->generaltexth->registerString(source.meta, ret->getNameTextID(), source["name"].String()); - VLC->generaltexth->registerString(source.meta, ret->getDescriptionTextID(), source["description"].String()); + VLC->generaltexth->registerString(source.getModScope(), ret->getNameTextID(), source["name"].String()); + VLC->generaltexth->registerString(source.getModScope(), ret->getDescriptionTextID(), source["description"].String()); ret->resources = TResources(source["cost"]); ret->produce = TResources(source["produce"]); @@ -729,7 +729,7 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons ret->town->buildings[ret->bid] = ret; - registerObject(source.meta, ret->town->getBuildingScope(), ret->identifier, ret->bid.getNum()); + registerObject(source.getModScope(), ret->town->getBuildingScope(), ret->identifier, ret->bid.getNum()); } void CTownHandler::loadBuildings(CTown * town, const JsonNode & source) @@ -751,14 +751,14 @@ void CTownHandler::loadStructure(CTown &town, const std::string & stringID, cons ret->building = nullptr; ret->buildable = nullptr; - VLC->identifiers()->tryRequestIdentifier( source.meta, "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable + VLC->identifiers()->tryRequestIdentifier( source.getModScope(), "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable { ret->building = town.buildings[BuildingID(identifier)]; }); if (source["builds"].isNull()) { - VLC->identifiers()->tryRequestIdentifier( source.meta, "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable + VLC->identifiers()->tryRequestIdentifier( source.getModScope(), "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable { ret->building = town.buildings[BuildingID(identifier)]; }); @@ -944,7 +944,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source) } else { - VLC->identifiers()->requestIdentifier( source.meta, "spell", "castleMoat", [=](si32 ability) + VLC->identifiers()->requestIdentifier( source.getModScope(), "spell", "castleMoat", [=](si32 ability) { town->moatAbility = SpellID(ability); }); @@ -984,7 +984,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source) { int chance = static_cast(node.second.Float()); - VLC->identifiers()->requestIdentifier(node.second.meta, "heroClass",node.first, [=](si32 classID) + VLC->identifiers()->requestIdentifier(node.second.getModScope(), "heroClass",node.first, [=](si32 classID) { VLC->heroclassesh->objects[classID]->selectionProbability[town->faction->getId()] = chance; }); @@ -994,7 +994,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source) { int chance = static_cast(node.second.Float()); - VLC->identifiers()->requestIdentifier(node.second.meta, "spell", node.first, [=](si32 spellID) + VLC->identifiers()->requestIdentifier(node.second.getModScope(), "spell", node.first, [=](si32 spellID) { VLC->spellh->objects.at(spellID)->probabilities[town->faction->getId()] = chance; }); @@ -1120,9 +1120,9 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod // register town once objects are loaded JsonNode config = data["town"]["mapObject"]; config["faction"].String() = name; - config["faction"].meta = scope; - if (config.meta.empty())// MODS COMPATIBILITY FOR 0.96 - config.meta = scope; + config["faction"].setModScope(scope, false); + if (config.getModScope().empty())// MODS COMPATIBILITY FOR 0.96 + config.setModScope(scope, false); VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index); // MODS COMPATIBILITY FOR 0.96 @@ -1163,7 +1163,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod // register town once objects are loaded JsonNode config = data["town"]["mapObject"]; config["faction"].String() = name; - config["faction"].meta = scope; + config["faction"].setModScope(scope, false); VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index); }); } @@ -1174,7 +1174,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod void CTownHandler::loadRandomFaction() { JsonNode randomFactionJson(JsonPath::builtin("config/factions/random.json")); - randomFactionJson.setMeta(ModScope::scopeBuiltin(), true); + randomFactionJson.setModScope(ModScope::scopeBuiltin(), true); loadBuildings(randomTown, randomFactionJson["random"]["town"]["buildings"]); } diff --git a/lib/bonuses/BonusParams.cpp b/lib/bonuses/BonusParams.cpp index 7274c0d9e..5b2765678 100644 --- a/lib/bonuses/BonusParams.cpp +++ b/lib/bonuses/BonusParams.cpp @@ -353,7 +353,7 @@ const JsonNode & BonusParams::toJson() ret["targetSourceType"].String() = vstd::findKey(bonusSourceMap, *targetType); jsonCreated = true; } - ret.setMeta(ModScope::scopeGame()); + ret.setModScope(ModScope::scopeGame()); return ret; }; diff --git a/lib/json/JsonBonus.cpp b/lib/json/JsonBonus.cpp index 75437c8bb..5288560b0 100644 --- a/lib/json/JsonBonus.cpp +++ b/lib/json/JsonBonus.cpp @@ -40,14 +40,14 @@ static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const Jso if (node.isNumber()) // Compatibility code for 1.3 or older { - logMod->warn("Bonus subtype must be string! (%s)", node.meta); + logMod->warn("Bonus subtype must be string! (%s)", node.getModScope()); subtype = BonusCustomSubtype(node.Integer()); return; } if (!node.isString()) { - logMod->warn("Bonus subtype must be string! (%s)", node.meta); + logMod->warn("Bonus subtype must be string! (%s)", node.getModScope()); subtype = BonusSubtypeID(); return; } diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index 290e3a647..bd41d2549 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -124,9 +124,14 @@ JsonNode::JsonType JsonNode::getType() const return static_cast(data.index()); } -void JsonNode::setMeta(const std::string & metadata, bool recursive) +const std::string & JsonNode::getModScope() const { - meta = metadata; + return modScope; +} + +void JsonNode::setModScope(const std::string & metadata, bool recursive) +{ + modScope = metadata; if (recursive) { switch (getType()) @@ -135,14 +140,14 @@ void JsonNode::setMeta(const std::string & metadata, bool recursive) { for(auto & node : Vector()) { - node.setMeta(metadata); + node.setModScope(metadata); } } break; case JsonType::DATA_STRUCT: { for(auto & node : Struct()) { - node.second.setMeta(metadata); + node.second.setModScope(metadata); } } } diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index 14374c09d..3962316c0 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -41,9 +41,9 @@ private: JsonData data; + /// Mod-origin of this particular field + std::string modScope; public: - /// free to use metadata fields - std::string meta; /// meta-flags like override std::vector flags; @@ -68,7 +68,8 @@ public: bool operator == (const JsonNode &other) const; bool operator != (const JsonNode &other) const; - void setMeta(const std::string & metadata, bool recursive = true); + const std::string & getModScope() const; + void setModScope(const std::string & metadata, bool recursive = true); /// Convert node to another type. Converting to nullptr will clear all data void setType(JsonType Type); @@ -130,7 +131,7 @@ public: template void serialize(Handler &h) { - h & meta; + h & modScope; h & flags; h & data; } diff --git a/lib/json/JsonRandom.cpp b/lib/json/JsonRandom.cpp index 9490ce244..1c5234ac8 100644 --- a/lib/json/JsonRandom.cpp +++ b/lib/json/JsonRandom.cpp @@ -321,7 +321,7 @@ VCMI_LIB_NAMESPACE_BEGIN { for(const auto & pair : value.Struct()) { - PrimarySkill id = decodeKey(pair.second.meta, pair.first, variables); + PrimarySkill id = decodeKey(pair.second.getModScope(), pair.first, variables); ret[id.getNum()] += loadValue(pair.second, rng, variables); } } @@ -357,7 +357,7 @@ VCMI_LIB_NAMESPACE_BEGIN { for(const auto & pair : value.Struct()) { - SecondarySkill id = decodeKey(pair.second.meta, pair.first, variables); + SecondarySkill id = decodeKey(pair.second.getModScope(), pair.first, variables); ret[id] = loadValue(pair.second, rng, variables); } } diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 643ef1488..1c9b2be20 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -230,7 +230,7 @@ void JsonUtils::merge(JsonNode & dest, JsonNode & source, bool ignoreOverride, b else { if (copyMeta) - dest.meta = source.meta; + dest.setModScope(source.getModScope(), false); //recursively merge all entries from struct for(auto & node : source.Struct()) diff --git a/lib/json/JsonValidator.cpp b/lib/json/JsonValidator.cpp index a894282e8..b4090af82 100644 --- a/lib/json/JsonValidator.cpp +++ b/lib/json/JsonValidator.cpp @@ -469,45 +469,45 @@ namespace std::string textFile(const JsonNode & node) { - TEST_FILE(node.meta, "", node.String(), EResType::JSON); + TEST_FILE(node.getModScope(), "", node.String(), EResType::JSON); return "Text file \"" + node.String() + "\" was not found"; } std::string musicFile(const JsonNode & node) { - TEST_FILE(node.meta, "Music/", node.String(), EResType::SOUND); - TEST_FILE(node.meta, "", node.String(), EResType::SOUND); + TEST_FILE(node.getModScope(), "Music/", node.String(), EResType::SOUND); + TEST_FILE(node.getModScope(), "", node.String(), EResType::SOUND); return "Music file \"" + node.String() + "\" was not found"; } std::string soundFile(const JsonNode & node) { - TEST_FILE(node.meta, "Sounds/", node.String(), EResType::SOUND); + TEST_FILE(node.getModScope(), "Sounds/", node.String(), EResType::SOUND); return "Sound file \"" + node.String() + "\" was not found"; } std::string defFile(const JsonNode & node) { - return testAnimation(node.String(), node.meta); + return testAnimation(node.String(), node.getModScope()); } std::string animationFile(const JsonNode & node) { - return testAnimation(node.String(), node.meta); + return testAnimation(node.String(), node.getModScope()); } std::string imageFile(const JsonNode & node) { - TEST_FILE(node.meta, "Data/", node.String(), EResType::IMAGE); - TEST_FILE(node.meta, "Sprites/", node.String(), EResType::IMAGE); + TEST_FILE(node.getModScope(), "Data/", node.String(), EResType::IMAGE); + TEST_FILE(node.getModScope(), "Sprites/", node.String(), EResType::IMAGE); if (node.String().find(':') != std::string::npos) - return testAnimation(node.String().substr(0, node.String().find(':')), node.meta); + return testAnimation(node.String().substr(0, node.String().find(':')), node.getModScope()); return "Image file \"" + node.String() + "\" was not found"; } std::string videoFile(const JsonNode & node) { - TEST_FILE(node.meta, "Video/", node.String(), EResType::VIDEO); + TEST_FILE(node.getModScope(), "Video/", node.String(), EResType::VIDEO); return "Video file \"" + node.String() + "\" was not found"; } diff --git a/lib/json/JsonWriter.cpp b/lib/json/JsonWriter.cpp index 2a4092b65..f777a18b4 100644 --- a/lib/json/JsonWriter.cpp +++ b/lib/json/JsonWriter.cpp @@ -37,8 +37,8 @@ void JsonWriter::writeEntry(JsonMap::const_iterator entry) { if(!compactMode) { - if (!entry->second.meta.empty()) - out << prefix << " // " << entry->second.meta << "\n"; + if (!entry->second.getModScope().empty()) + out << prefix << " // " << entry->second.getModScope() << "\n"; if(!entry->second.flags.empty()) out << prefix << " // flags: " << boost::algorithm::join(entry->second.flags, ", ") << "\n"; out << prefix; @@ -52,8 +52,8 @@ void JsonWriter::writeEntry(JsonVector::const_iterator entry) { if(!compactMode) { - if (!entry->meta.empty()) - out << prefix << " // " << entry->meta << "\n"; + if (!entry->getModScope().empty()) + out << prefix << " // " << entry->getModScope() << "\n"; if(!entry->flags.empty()) out << prefix << " // flags: " << boost::algorithm::join(entry->flags, ", ") << "\n"; out << prefix; diff --git a/lib/mapObjectConstructors/CBankInstanceConstructor.cpp b/lib/mapObjectConstructors/CBankInstanceConstructor.cpp index 170aa1239..00c800173 100644 --- a/lib/mapObjectConstructors/CBankInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/CBankInstanceConstructor.cpp @@ -27,7 +27,7 @@ void CBankInstanceConstructor::initTypeData(const JsonNode & input) if (input.Struct().count("name") == 0) logMod->warn("Bank %s missing name!", getJsonKey()); - VLC->generaltexth->registerString(input.meta, getNameTextID(), input["name"].String()); + VLC->generaltexth->registerString(input.getModScope(), getNameTextID(), input["name"].String()); levels = input["levels"].Vector(); bankResetDuration = static_cast(input["resetDuration"].Float()); diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index e6ee4f2dd..147590e2b 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -259,21 +259,21 @@ std::unique_ptr CObjectClassesHandler::loadFromJson(const std::stri { if (!subData.second["index"].isNull()) { - const std::string & subMeta = subData.second["index"].meta; + const std::string & subMeta = subData.second["index"].getModScope(); if ( subMeta == "core") { size_t subIndex = subData.second["index"].Integer(); - loadSubObject(subData.second.meta, subData.first, subData.second, obj.get(), subIndex); + loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get(), subIndex); } else { logMod->error("Object %s:%s.%s - attempt to load object with preset index! This option is reserved for built-in mod", subMeta, name, subData.first ); - loadSubObject(subData.second.meta, subData.first, subData.second, obj.get()); + loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get()); } } else - loadSubObject(subData.second.meta, subData.first, subData.second, obj.get()); + loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get()); } if (obj->id == MapObjectID::MONOLITH_TWO_WAY) @@ -306,7 +306,7 @@ void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNo objects.at(ID.getNum())->objects.resize(subID.getNum()+1); JsonUtils::inherit(config, objects.at(ID.getNum())->base); - loadSubObject(config.meta, identifier, config, objects.at(ID.getNum()).get(), subID.getNum()); + loadSubObject(config.getModScope(), identifier, config, objects.at(ID.getNum()).get(), subID.getNum()); } void CObjectClassesHandler::removeSubObject(MapObjectID ID, MapObjectSubID subID) diff --git a/lib/mapObjectConstructors/CRewardableConstructor.cpp b/lib/mapObjectConstructors/CRewardableConstructor.cpp index 83242529b..72d61e796 100644 --- a/lib/mapObjectConstructors/CRewardableConstructor.cpp +++ b/lib/mapObjectConstructors/CRewardableConstructor.cpp @@ -22,7 +22,7 @@ void CRewardableConstructor::initTypeData(const JsonNode & config) blockVisit = config["blockedVisitable"].Bool(); if (!config["name"].isNull()) - VLC->generaltexth->registerString( config.meta, getNameTextID(), config["name"].String()); + VLC->generaltexth->registerString( config.getModScope(), getNameTextID(), config["name"].String()); } diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index 41ea8f9fc..806b32194 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -67,7 +67,7 @@ void CTownInstanceConstructor::initTypeData(const JsonNode & input) // change scope of "filters" to scope of object that is being loaded // since this filters require to resolve building ID's - filtersJson.setMeta(input["faction"].meta); + filtersJson.setModScope(input["faction"].getModScope()); } void CTownInstanceConstructor::afterLoadFinalization() diff --git a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp index c52281505..83f20becf 100644 --- a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp @@ -29,7 +29,7 @@ void DwellingInstanceConstructor::initTypeData(const JsonNode & input) if (input.Struct().count("name") == 0) logMod->warn("Dwelling %s missing name!", getJsonKey()); - VLC->generaltexth->registerString( input.meta, getNameTextID(), input["name"].String()); + VLC->generaltexth->registerString( input.getModScope(), getNameTextID(), input["name"].String()); const JsonVector & levels = input["creatures"].Vector(); const auto totalLevels = levels.size(); diff --git a/lib/mapping/CMapService.cpp b/lib/mapping/CMapService.cpp index f78a67ee5..cf70f7b08 100644 --- a/lib/mapping/CMapService.cpp +++ b/lib/mapping/CMapService.cpp @@ -171,7 +171,7 @@ static JsonNode loadPatches(const std::string & path) for (auto & entry : node.Struct()) JsonUtils::validate(entry.second, "vcmi:mapHeader", "patch for " + entry.first); - node.setMeta(ModScope::scopeMap()); + node.setModScope(ModScope::scopeMap()); return node; } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index c9a2a9b40..3f3c64606 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1153,7 +1153,7 @@ CGObjectInstance * CMapLoaderH3M::readWitchHut(const int3 & position, std::share variable["anyOf"].Vector() = anyOfList; } - variable.setMeta(ModScope::scopeGame()); // list may include skills from all mods + variable.setModScope(ModScope::scopeGame()); // list may include skills from all mods rewardable->configuration.presetVariable("secondarySkill", "gainedSkill", variable); } else @@ -1189,7 +1189,7 @@ CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared JsonNode variable; JsonNode dice; variable.String() = NPrimarySkill::names[bonusID]; - variable.setMeta(ModScope::scopeGame()); + variable.setModScope(ModScope::scopeGame()); dice.Integer() = 80; rewardable->configuration.presetVariable("primarySkill", "gainedStat", variable); rewardable->configuration.presetVariable("dice", "0", dice); @@ -1200,7 +1200,7 @@ CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared JsonNode variable; JsonNode dice; variable.String() = VLC->skills()->getByIndex(bonusID)->getJsonKey(); - variable.setMeta(ModScope::scopeGame()); + variable.setModScope(ModScope::scopeGame()); dice.Integer() = 50; rewardable->configuration.presetVariable("secondarySkill", "gainedSkill", variable); rewardable->configuration.presetVariable("dice", "0", dice); @@ -1211,7 +1211,7 @@ CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared JsonNode variable; JsonNode dice; variable.String() = VLC->spells()->getByIndex(bonusID)->getJsonKey(); - variable.setMeta(ModScope::scopeGame()); + variable.setModScope(ModScope::scopeGame()); dice.Integer() = 20; rewardable->configuration.presetVariable("spell", "gainedSpell", variable); rewardable->configuration.presetVariable("dice", "0", dice); @@ -1356,7 +1356,7 @@ CGObjectInstance * CMapLoaderH3M::readShrine(const int3 & position, std::shared_ { JsonNode variable; variable.String() = VLC->spells()->getById(spell)->getJsonKey(); - variable.setMeta(ModScope::scopeGame()); // list may include spells from all mods + variable.setModScope(ModScope::scopeGame()); // list may include spells from all mods rewardable->configuration.presetVariable("spell", "gainedSpell", variable); } } diff --git a/lib/mapping/MapIdentifiersH3M.cpp b/lib/mapping/MapIdentifiersH3M.cpp index 15a7602ce..f51334a2a 100644 --- a/lib/mapping/MapIdentifiersH3M.cpp +++ b/lib/mapping/MapIdentifiersH3M.cpp @@ -28,7 +28,7 @@ void MapIdentifiersH3M::loadMapping(std::map & resul for (auto entry : mapping.Struct()) { IdentifierID sourceID (entry.second.Integer()); - IdentifierID targetID (*VLC->identifiers()->getIdentifier(entry.second.meta, identifierName, entry.first)); + IdentifierID targetID (*VLC->identifiers()->getIdentifier(entry.second.getModScope(), identifierName, entry.first)); result[sourceID] = targetID; } @@ -41,13 +41,13 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping) for (auto entryFaction : mapping["buildings"].Struct()) { - FactionID factionID (*VLC->identifiers()->getIdentifier(entryFaction.second.meta, "faction", entryFaction.first)); + FactionID factionID (*VLC->identifiers()->getIdentifier(entryFaction.second.getModScope(), "faction", entryFaction.first)); auto buildingMap = entryFaction.second; for (auto entryBuilding : buildingMap.Struct()) { BuildingID sourceID (entryBuilding.second.Integer()); - BuildingID targetID (*VLC->identifiers()->getIdentifier(entryBuilding.second.meta, "building." + VLC->factions()->getById(factionID)->getJsonKey(), entryBuilding.first)); + BuildingID targetID (*VLC->identifiers()->getIdentifier(entryBuilding.second.getModScope(), "building." + VLC->factions()->getById(factionID)->getJsonKey(), entryBuilding.first)); mappingFactionBuilding[factionID][sourceID] = targetID; } @@ -70,7 +70,7 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping) { for (auto entryInner : entryOuter.second.Struct()) { - auto handler = VLC->objtypeh->getHandlerFor( entryInner.second.meta, entryOuter.first, entryInner.first); + auto handler = VLC->objtypeh->getHandlerFor( entryInner.second.getModScope(), entryOuter.first, entryInner.first); auto entryValues = entryInner.second.Vector(); ObjectTypeIdentifier h3mID{Obj(entryValues[0].Integer()), int32_t(entryValues[1].Integer())}; @@ -80,7 +80,7 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping) } else { - auto handler = VLC->objtypeh->getHandlerFor( entryOuter.second.meta, entryOuter.first, entryOuter.first); + auto handler = VLC->objtypeh->getHandlerFor( entryOuter.second.getModScope(), entryOuter.first, entryOuter.first); auto entryValues = entryOuter.second.Vector(); ObjectTypeIdentifier h3mID{Obj(entryValues[0].Integer()), int32_t(entryValues[1].Integer())}; diff --git a/lib/modding/CModInfo.cpp b/lib/modding/CModInfo.cpp index dab87979a..8690c17d0 100644 --- a/lib/modding/CModInfo.cpp +++ b/lib/modding/CModInfo.cpp @@ -18,7 +18,7 @@ VCMI_LIB_NAMESPACE_BEGIN static JsonNode addMeta(JsonNode config, const std::string & meta) { - config.setMeta(meta); + config.setModScope(meta); return config; } diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index 0c2abf99a..5817188e8 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -45,7 +45,7 @@ ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string { for(auto & node : originalData) { - node.setMeta(ModScope::scopeBuiltin()); + node.setModScope(ModScope::scopeBuiltin()); } } @@ -53,7 +53,7 @@ bool ContentTypeHandler::preloadModData(const std::string & modName, const std:: { bool result = false; JsonNode data = JsonUtils::assembleFromFiles(fileList, result); - data.setMeta(modName); + data.setModScope(modName); ModInfo & modInfo = modData[modName]; @@ -104,7 +104,7 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate) const std::string & name = entry.first; JsonNode & data = entry.second; - if (data.meta != modName) + if (data.getModScope() != modName) { // in this scenario, entire object record comes from another mod // normally, this is used to "patch" object from another mod (which is legal) @@ -112,7 +112,7 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate) // - another mod attempts to add object into this mod (technically can be supported, but might lead to weird edge cases) // - another mod attempts to edit object from this mod that no longer exist - DANGER since such patch likely has very incomplete data // so emit warning and skip such case - logMod->warn("Mod '%s' attempts to edit object '%s' of type '%s' from mod '%s' but no such object exist!", data.meta, name, objectName, modName); + logMod->warn("Mod '%s' attempts to edit object '%s' of type '%s' from mod '%s' but no such object exist!", data.getModScope(), name, objectName, modName); continue; } @@ -163,7 +163,7 @@ void ContentTypeHandler::afterLoadFinalization() if (data.second.modData.isNull()) { for (auto node : data.second.patches.Struct()) - logMod->warn("Mod '%s' have added patch for object '%s' from mod '%s', but this mod was not loaded or has no new objects.", node.second.meta, node.first, data.first); + logMod->warn("Mod '%s' have added patch for object '%s' from mod '%s', but this mod was not loaded or has no new objects.", node.second.getModScope(), node.first, data.first); } for(auto & otherMod : modData) diff --git a/lib/modding/IdentifierStorage.cpp b/lib/modding/IdentifierStorage.cpp index dd17b15ae..589a79f44 100644 --- a/lib/modding/IdentifierStorage.cpp +++ b/lib/modding/IdentifierStorage.cpp @@ -180,12 +180,12 @@ void CIdentifierStorage::requestIdentifier(const std::string & scope, const std: void CIdentifierStorage::requestIdentifier(const std::string & type, const JsonNode & name, const std::function & callback) const { - requestIdentifier(ObjectCallback::fromNameAndType(name.meta, type, name.String(), callback, false)); + requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, false)); } void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::function & callback) const { - requestIdentifier(ObjectCallback::fromNameWithType(name.meta, name.String(), callback, false)); + requestIdentifier(ObjectCallback::fromNameWithType(name.getModScope(), name.String(), callback, false)); } void CIdentifierStorage::tryRequestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function & callback) const @@ -195,7 +195,7 @@ void CIdentifierStorage::tryRequestIdentifier(const std::string & scope, const s void CIdentifierStorage::tryRequestIdentifier(const std::string & type, const JsonNode & name, const std::function & callback) const { - requestIdentifier(ObjectCallback::fromNameAndType(name.meta, type, name.String(), callback, true)); + requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, true)); } std::optional CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & type, const std::string & name, bool silent) const @@ -210,7 +210,7 @@ std::optional CIdentifierStorage::getIdentifier(const std::string & type, { assert(state != ELoadingState::LOADING); - auto options = ObjectCallback::fromNameAndType(name.meta, type, name.String(), std::function(), silent); + auto options = ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), std::function(), silent); return getIdentifierImpl(options, silent); } @@ -219,7 +219,7 @@ std::optional CIdentifierStorage::getIdentifier(const JsonNode & name, boo { assert(state != ELoadingState::LOADING); - auto options = ObjectCallback::fromNameWithType(name.meta, name.String(), std::function(), silent); + auto options = ObjectCallback::fromNameWithType(name.getModScope(), name.String(), std::function(), silent); return getIdentifierImpl(options, silent); } diff --git a/lib/rewardable/Info.cpp b/lib/rewardable/Info.cpp index 8a6fa89cc..14b73fa6c 100644 --- a/lib/rewardable/Info.cpp +++ b/lib/rewardable/Info.cpp @@ -75,7 +75,7 @@ void Rewardable::Info::init(const JsonNode & objectConfig, const std::string & o auto loadString = [&](const JsonNode & entry, const TextIdentifier & textID){ if (entry.isString() && !entry.String().empty() && entry.String()[0] != '@') - VLC->generaltexth->registerString(entry.meta, textID, entry.String()); + VLC->generaltexth->registerString(entry.getModScope(), textID, entry.String()); }; parameters = objectConfig; @@ -201,8 +201,8 @@ void Rewardable::Info::configureReward(Rewardable::Configuration & object, CRand for ( auto node : source["changeCreatures"].Struct() ) { - CreatureID from(VLC->identifiers()->getIdentifier(node.second.meta, "creature", node.first).value()); - CreatureID dest(VLC->identifiers()->getIdentifier(node.second.meta, "creature", node.second.String()).value()); + CreatureID from(VLC->identifiers()->getIdentifier(node.second.getModScope(), "creature", node.first).value()); + CreatureID dest(VLC->identifiers()->getIdentifier(node.second.getModScope(), "creature", node.second.String()).value()); reward.extraComponents.emplace_back(ComponentType::CREATURE, dest); diff --git a/lib/serializer/JsonDeserializer.cpp b/lib/serializer/JsonDeserializer.cpp index 15d20e989..87cda93d6 100644 --- a/lib/serializer/JsonDeserializer.cpp +++ b/lib/serializer/JsonDeserializer.cpp @@ -43,7 +43,7 @@ void JsonDeserializer::serializeInternal(const std::string & fieldName, si32 & v if(rawId < 0) //may be, user has installed the mod into another directory... { auto internalId = vstd::splitStringToPair(identifier, ':').second; - auto currentScope = getCurrent().meta; + auto currentScope = getCurrent().getModScope(); auto actualId = currentScope.length() > 0 ? currentScope + ":" + internalId : internalId; rawId = decoder(actualId); diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 97df5943e..aaf101276 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -719,7 +719,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & { const int chance = static_cast(node.second.Integer()); - VLC->identifiers()->requestIdentifier(node.second.meta, "faction", node.first, [=](si32 factionID) + VLC->identifiers()->requestIdentifier(node.second.getModScope(), "faction", node.first, [=](si32 factionID) { spell->probabilities[FactionID(factionID)] = chance; }); @@ -742,7 +742,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & { if(counteredSpell.second.Bool()) { - VLC->identifiers()->requestIdentifier(counteredSpell.second.meta, "spell", counteredSpell.first, [=](si32 id) + VLC->identifiers()->requestIdentifier(counteredSpell.second.getModScope(), "spell", counteredSpell.first, [=](si32 id) { spell->counteredSpells.emplace_back(id); }); diff --git a/lib/spells/TargetCondition.cpp b/lib/spells/TargetCondition.cpp index 7b9407afe..c55e18719 100644 --- a/lib/spells/TargetCondition.cpp +++ b/lib/spells/TargetCondition.cpp @@ -544,7 +544,7 @@ void TargetCondition::loadConditions(const JsonNode & source, bool exclusive, bo ModUtility::parseIdentifier(keyValue.first, scope, type, identifier); - item = itemFactory->createConfigurable(keyValue.second.meta, type, identifier); + item = itemFactory->createConfigurable(keyValue.second.getModScope(), type, identifier); } if(item) diff --git a/scripting/lua/LuaScriptingContext.cpp b/scripting/lua/LuaScriptingContext.cpp index d92e43735..47aacb458 100644 --- a/scripting/lua/LuaScriptingContext.cpp +++ b/scripting/lua/LuaScriptingContext.cpp @@ -347,8 +347,8 @@ void LuaContext::pop(JsonNode & value) break; case LUA_TTABLE: { - JsonNode asVector(JsonNode::JsonType::DATA_VECTOR); - JsonNode asStruct(JsonNode::JsonType::DATA_STRUCT); + JsonNode asVector; + JsonNode asStruct; lua_pushnil(L); /* first key */ diff --git a/scripting/lua/LuaSpellEffect.cpp b/scripting/lua/LuaSpellEffect.cpp index 484235af6..91db47955 100644 --- a/scripting/lua/LuaSpellEffect.cpp +++ b/scripting/lua/LuaSpellEffect.cpp @@ -98,12 +98,12 @@ bool LuaSpellEffect::applicable(Problem & problem, const Mechanics * m, const Ef for(const auto & dest : target) { JsonNode targetData; - targetData.Vector().push_back(JsonUtils::intNode(dest.hexValue.hex)); + targetData.Vector().emplace_back(dest.hexValue.hex); if(dest.unitValue) - targetData.Vector().push_back(JsonUtils::intNode(dest.unitValue->unitId())); + targetData.Vector().emplace_back(dest.unitValue->unitId()); else - targetData.Vector().push_back(JsonUtils::intNode(-1)); + targetData.Vector().emplace_back(-1); requestP.Vector().push_back(targetData); } @@ -141,12 +141,12 @@ void LuaSpellEffect::apply(ServerCallback * server, const Mechanics * m, const E for(const auto & dest : target) { JsonNode targetData; - targetData.Vector().push_back(JsonUtils::intNode(dest.hexValue.hex)); + targetData.Vector().emplace_back(dest.hexValue.hex); if(dest.unitValue) - targetData.Vector().push_back(JsonUtils::intNode(dest.unitValue->unitId())); + targetData.Vector().emplace_back(dest.unitValue->unitId()); else - targetData.Vector().push_back(JsonUtils::intNode(-1)); + targetData.Vector().emplace_back(-1); requestP.Vector().push_back(targetData); } diff --git a/scripting/lua/LuaStack.cpp b/scripting/lua/LuaStack.cpp index b406a278d..71f4798c3 100644 --- a/scripting/lua/LuaStack.cpp +++ b/scripting/lua/LuaStack.cpp @@ -183,8 +183,8 @@ bool LuaStack::tryGet(int position, JsonNode & value) return tryGet(position, value.String()); case LUA_TTABLE: { - JsonNode asVector(JsonNode::JsonType::DATA_VECTOR); - JsonNode asStruct(JsonNode::JsonType::DATA_STRUCT); + JsonNode asVector; + JsonNode asStruct; lua_pushnil(L); /* first key */ diff --git a/test/entity/CCreatureTest.cpp b/test/entity/CCreatureTest.cpp index 3ffdd4d7d..590754e73 100644 --- a/test/entity/CCreatureTest.cpp +++ b/test/entity/CCreatureTest.cpp @@ -49,7 +49,7 @@ TEST_F(CCreatureTest, RegistersIcons) TEST_F(CCreatureTest, DISABLED_JsonUpdate) { - JsonNode data(JsonNode::JsonType::DATA_STRUCT); + JsonNode data; JsonNode & config = data["config"]; config["cost"]["gold"].Integer() = 750; @@ -106,7 +106,7 @@ TEST_F(CCreatureTest, DISABLED_JsonUpdate) TEST_F(CCreatureTest, DISABLED_JsonAddBonus) { - JsonNode data(JsonNode::JsonType::DATA_STRUCT); + JsonNode data; auto b = std::make_shared(BonusDuration::PERMANENT, BonusType::BLOCKS_RETALIATION, BonusSource::CREATURE_ABILITY, 17, BonusSourceID(CreatureID(42)), BonusSubtypeID(CreatureID(43)), BonusValueType::BASE_NUMBER); @@ -132,7 +132,7 @@ TEST_F(CCreatureTest, DISABLED_JsonAddBonus) TEST_F(CCreatureTest, DISABLED_JsonRemoveBonus) { - JsonNode data(JsonNode::JsonType::DATA_STRUCT); + JsonNode data; auto b1 = std::make_shared(BonusDuration::PERMANENT, BonusType::BLOCKS_RETALIATION, BonusSource::CREATURE_ABILITY, 17, BonusSourceID(CreatureID(42)), BonusSubtypeID(CreatureID(43)), BonusValueType::BASE_NUMBER); subject->addNewBonus(b1); diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index 29a6a43d5..aff72a61f 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -420,6 +420,6 @@ TEST_F(CGameStateTest, updateEntity) JsonNode actual; EXPECT_CALL(services, updateEntity(Eq(Metatype::CREATURE), Eq(424242), _)).WillOnce(SaveArg<2>(&actual)); - gameState->updateEntity(Metatype::CREATURE, 424242, JsonUtils::stringNode("TEST")); + gameState->updateEntity(Metatype::CREATURE, 424242, JsonNode("TEST")); EXPECT_EQ(actual.String(), "TEST"); } diff --git a/test/map/CMapFormatTest.cpp b/test/map/CMapFormatTest.cpp index 6d6cd9e54..176d5d916 100644 --- a/test/map/CMapFormatTest.cpp +++ b/test/map/CMapFormatTest.cpp @@ -95,7 +95,7 @@ static JsonNode getFromArchive(CZipLoader & archive, const std::string & archive auto data = archive.load(resource)->readAll(); - JsonNode res(reinterpret_cast(data.first.get()), data.second); + JsonNode res(reinterpret_cast(data.first.get()), data.second); return res; } diff --git a/test/scripting/LuaSpellEffectAPITest.cpp b/test/scripting/LuaSpellEffectAPITest.cpp index 78958dfa7..dd3364057 100644 --- a/test/scripting/LuaSpellEffectAPITest.cpp +++ b/test/scripting/LuaSpellEffectAPITest.cpp @@ -46,7 +46,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplicableOnExpert) JsonNode ret = context->callGlobal("applicable", params); - JsonNode expected = JsonUtils::boolNode(true); + JsonNode expected(true); JsonComparer cmp(false); cmp.compare("applicable result", ret, expected); @@ -65,7 +65,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_NotApplicableOnAdvanced) JsonNode ret = context->callGlobal("applicable", params); - JsonNode expected = JsonUtils::boolNode(false); + JsonNode expected(false); JsonComparer cmp(false); cmp.compare("applicable result", ret, expected); @@ -84,7 +84,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplicableOnLeftSideOfField) BattleHex hex(2,2); JsonNode first; - first.Vector().push_back(JsonUtils::intNode(hex.hex)); + first.Vector().push_back(JsonNode(hex.hex)); first.Vector().push_back(JsonNode()); JsonNode targets; @@ -94,7 +94,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplicableOnLeftSideOfField) JsonNode ret = context->callGlobal("applicableTarget", params); - JsonNode expected = JsonUtils::boolNode(true); + JsonNode expected(true); JsonComparer cmp(false); cmp.compare("applicable result", ret, expected); @@ -113,8 +113,8 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_NotApplicableOnRightSideOfField) BattleHex hex(11,2); JsonNode first; - first.Vector().push_back(JsonUtils::intNode(hex.hex)); - first.Vector().push_back(JsonUtils::intNode(-1)); + first.Vector().emplace_back(hex.hex); + first.Vector().emplace_back(-1); JsonNode targets; targets.Vector().push_back(first); @@ -123,7 +123,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_NotApplicableOnRightSideOfField) JsonNode ret = context->callGlobal("applicableTarget", params); - JsonNode expected = JsonUtils::boolNode(false); + JsonNode expected(false); JsonComparer cmp(false); cmp.compare("applicable result", ret, expected); @@ -138,14 +138,14 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplyMoveUnit) BattleHex hex1(11,2); JsonNode unit; - unit.Vector().push_back(JsonUtils::intNode(hex1.hex)); - unit.Vector().push_back(JsonUtils::intNode(42)); + unit.Vector().emplace_back(hex1.hex); + unit.Vector().emplace_back(42); BattleHex hex2(5,4); JsonNode destination; - destination.Vector().push_back(JsonUtils::intNode(hex2.hex)); - destination.Vector().push_back(JsonUtils::intNode(-1)); + destination.Vector().emplace_back(hex2.hex); + destination.Vector().emplace_back(-1); JsonNode targets; targets.Vector().push_back(unit); diff --git a/test/scripting/LuaSpellEffectTest.cpp b/test/scripting/LuaSpellEffectTest.cpp index b917c9746..01a95b1a3 100644 --- a/test/scripting/LuaSpellEffectTest.cpp +++ b/test/scripting/LuaSpellEffectTest.cpp @@ -91,7 +91,7 @@ public: JsonNode saveRequest(const std::string &, const JsonNode & parameters) { - JsonNode response = JsonUtils::boolNode(true); + JsonNode response(true); request = parameters; return response; @@ -99,7 +99,7 @@ public: JsonNode saveRequest2(ServerCallback *, const std::string &, const JsonNode & parameters) { - JsonNode response = JsonUtils::boolNode(true); + JsonNode response(true); request = parameters; return response; @@ -123,7 +123,7 @@ TEST_F(LuaSpellEffectTest, ApplicableRedirected) { setDefaultExpectations(); - JsonNode response = JsonUtils::boolNode(true); + JsonNode response(true); EXPECT_CALL(*contextMock, callGlobal(Eq("applicable"),_)).WillOnce(Return(response));//TODO: check call parameter @@ -154,12 +154,12 @@ TEST_F(LuaSpellEffectTest, ApplicableTargetRedirected) JsonNode first; - first.Vector().push_back(JsonUtils::intNode(hex1.hex)); - first.Vector().push_back(JsonUtils::intNode(id1)); + first.Vector().push_back(JsonNode(hex1.hex)); + first.Vector().push_back(JsonNode(id1)); JsonNode second; - second.Vector().push_back(JsonUtils::intNode(hex2.hex)); - second.Vector().push_back(JsonUtils::intNode(-1)); + second.Vector().push_back(JsonNode(hex2.hex)); + second.Vector().push_back(JsonNode(-1)); JsonNode targets; targets.Vector().push_back(first); @@ -193,8 +193,8 @@ TEST_F(LuaSpellEffectTest, ApplyRedirected) subject->apply(&serverMock, &mechanicsMock, target); JsonNode first; - first.Vector().push_back(JsonUtils::intNode(hex1.hex)); - first.Vector().push_back(JsonUtils::intNode(id1)); + first.Vector().push_back(JsonNode(hex1.hex)); + first.Vector().push_back(JsonNode(id1)); JsonNode targets; targets.Vector().push_back(first); diff --git a/test/scripting/ScriptFixture.cpp b/test/scripting/ScriptFixture.cpp index aabcea131..0ff6e3b2b 100644 --- a/test/scripting/ScriptFixture.cpp +++ b/test/scripting/ScriptFixture.cpp @@ -23,7 +23,7 @@ ScriptFixture::~ScriptFixture() = default; void ScriptFixture::loadScriptFromFile(const std::string & path) { - JsonNode scriptConfig(JsonNode::JsonType::DATA_STRUCT); + JsonNode scriptConfig; scriptConfig["source"].String() = path; loadScript(scriptConfig); } diff --git a/test/spells/TargetConditionTest.cpp b/test/spells/TargetConditionTest.cpp index 2f6f604fb..7ed5d3ccf 100644 --- a/test/spells/TargetConditionTest.cpp +++ b/test/spells/TargetConditionTest.cpp @@ -122,7 +122,7 @@ TEST_F(TargetConditionTest, SerializesCorrectly) EXPECT_CALL(factoryMock, createConfigurable(Eq(""), Eq("bonus"), Eq("UNDEAD"))).WillOnce(Return(normalItem)); - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["noneOf"]["bonus.NON_LIVING"].String() = "normal"; config["anyOf"]["bonus.SIEGE_WEAPON"].String() = "absolute"; config["allOf"]["bonus.UNDEAD"].String() = "normal"; diff --git a/test/spells/effects/CatapultTest.cpp b/test/spells/effects/CatapultTest.cpp index 690d9cb5c..1cb55c5f2 100644 --- a/test/spells/effects/CatapultTest.cpp +++ b/test/spells/effects/CatapultTest.cpp @@ -116,7 +116,7 @@ private: TEST_F(CatapultApplyTest, DISABLED_DamageToIntactPart) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["targetsToAttack"].Integer() = 1; config["chanceToNormalHit"].Integer() = 100; EffectFixture::setupEffect(config); diff --git a/test/spells/effects/CloneTest.cpp b/test/spells/effects/CloneTest.cpp index 810824b6b..b68124242 100644 --- a/test/spells/effects/CloneTest.cpp +++ b/test/spells/effects/CloneTest.cpp @@ -40,7 +40,7 @@ protected: TEST_F(CloneTest, ApplicableToValidTarget) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["maxTier"].Integer() = 7; EffectFixture::setupEffect(config); } diff --git a/test/spells/effects/DamageTest.cpp b/test/spells/effects/DamageTest.cpp index a8bae9734..227764405 100644 --- a/test/spells/effects/DamageTest.cpp +++ b/test/spells/effects/DamageTest.cpp @@ -147,7 +147,7 @@ TEST_F(DamageApplyTest, DISABLED_DoesDamageByPercent) using namespace ::battle; { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["killByPercentage"].Bool() = true; EffectFixture::setupEffect(config); } @@ -192,7 +192,7 @@ TEST_F(DamageApplyTest, DISABLED_DoesDamageByCount) using namespace ::battle; { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["killByCount"].Bool() = true; EffectFixture::setupEffect(config); } diff --git a/test/spells/effects/DispelTest.cpp b/test/spells/effects/DispelTest.cpp index 5d05ea81a..fb53586b3 100644 --- a/test/spells/effects/DispelTest.cpp +++ b/test/spells/effects/DispelTest.cpp @@ -68,7 +68,7 @@ class DispelTest : public DispelFixture TEST_F(DispelTest, DISABLED_ApplicableToAliveUnitWithTimedEffect) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["dispelNegative"].Bool() = true; EffectFixture::setupEffect(config); } @@ -94,7 +94,7 @@ TEST_F(DispelTest, DISABLED_ApplicableToAliveUnitWithTimedEffect) TEST_F(DispelTest, DISABLED_IgnoresOwnEffects) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["dispelPositive"].Bool() = true; config["dispelNegative"].Bool() = true; config["dispelNeutral"].Bool() = true; @@ -165,7 +165,7 @@ public: TEST_F(DispelApplyTest, DISABLED_RemovesEffects) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["dispelPositive"].Bool() = true; config["dispelNegative"].Bool() = true; config["dispelNeutral"].Bool() = true; diff --git a/test/spells/effects/HealTest.cpp b/test/spells/effects/HealTest.cpp index d2479951c..8cfdb396b 100644 --- a/test/spells/effects/HealTest.cpp +++ b/test/spells/effects/HealTest.cpp @@ -77,7 +77,7 @@ TEST_F(HealTest, ApplicableToWoundedUnit) TEST_F(HealTest, ApplicableIfActuallyResurrects) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["healLevel"].String() = "resurrect"; config["minFullUnits"].Integer() = 5; EffectFixture::setupEffect(config); @@ -104,7 +104,7 @@ TEST_F(HealTest, ApplicableIfActuallyResurrects) TEST_F(HealTest, NotApplicableIfNotEnoughCasualties) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["healLevel"].String() = "resurrect"; config["minFullUnits"].Integer() = 1; EffectFixture::setupEffect(config); @@ -130,7 +130,7 @@ TEST_F(HealTest, NotApplicableIfNotEnoughCasualties) TEST_F(HealTest, NotApplicableIfResurrectsLessThanRequired) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["healLevel"].String() = "resurrect"; config["minFullUnits"].Integer() = 5; EffectFixture::setupEffect(config); @@ -156,7 +156,7 @@ TEST_F(HealTest, NotApplicableIfResurrectsLessThanRequired) TEST_F(HealTest, ApplicableToDeadUnit) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["healLevel"].String() = "resurrect"; EffectFixture::setupEffect(config); } @@ -187,7 +187,7 @@ TEST_F(HealTest, ApplicableToDeadUnit) TEST_F(HealTest, DISABLED_NotApplicableIfDeadUnitIsBlocked) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["healLevel"].String() = "resurrect"; EffectFixture::setupEffect(config); } @@ -224,7 +224,7 @@ TEST_F(HealTest, DISABLED_NotApplicableIfDeadUnitIsBlocked) TEST_F(HealTest, DISABLED_ApplicableWithAnotherDeadUnitInSamePosition) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["healLevel"].String() = "resurrect"; EffectFixture::setupEffect(config); } @@ -261,7 +261,7 @@ TEST_F(HealTest, DISABLED_ApplicableWithAnotherDeadUnitInSamePosition) TEST_F(HealTest, NotApplicableIfEffectValueTooLow) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["minFullUnits"].Integer() = 1; EffectFixture::setupEffect(config); } @@ -328,7 +328,7 @@ protected: TEST_P(HealApplyTest, DISABLED_Heals) { { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["healLevel"].String() = HEAL_LEVEL_MAP.at(static_cast(healLevel)); config["healPower"].String() = HEAL_POWER_MAP.at(static_cast(healPower)); EffectFixture::setupEffect(config); diff --git a/test/spells/effects/SacrificeTest.cpp b/test/spells/effects/SacrificeTest.cpp index 2bde689ef..40c04bc4e 100644 --- a/test/spells/effects/SacrificeTest.cpp +++ b/test/spells/effects/SacrificeTest.cpp @@ -39,7 +39,7 @@ protected: EffectFixture::setUp(); { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["healLevel"].String() = "resurrect"; EffectFixture::setupEffect(config); } @@ -146,7 +146,7 @@ protected: EffectFixture::setUp(); { - JsonNode config(JsonNode::JsonType::DATA_STRUCT); + JsonNode config; config["healLevel"].String() = "resurrect"; config["healPower"].String() = "permanent"; EffectFixture::setupEffect(config); diff --git a/test/spells/effects/SummonTest.cpp b/test/spells/effects/SummonTest.cpp index 2dfbfc659..fbc322340 100644 --- a/test/spells/effects/SummonTest.cpp +++ b/test/spells/effects/SummonTest.cpp @@ -73,7 +73,7 @@ protected: toSummon = creature1; - JsonNode options(JsonNode::JsonType::DATA_STRUCT); + JsonNode options; options["id"].String() = "airElemental"; options["exclusive"].Bool() = exclusive; options["summonSameUnit"].Bool() = summonSameUnit; @@ -202,7 +202,7 @@ protected: permanent = ::testing::get<0>(GetParam()); summonByHealth = ::testing::get<1>(GetParam()); - JsonNode options(JsonNode::JsonType::DATA_STRUCT); + JsonNode options; options["id"].String() = "airElemental"; options["permanent"].Bool() = permanent; options["summonByHealth"].Bool() = summonByHealth; diff --git a/test/spells/effects/TimedTest.cpp b/test/spells/effects/TimedTest.cpp index e96f3d476..541a35ecf 100644 --- a/test/spells/effects/TimedTest.cpp +++ b/test/spells/effects/TimedTest.cpp @@ -77,11 +77,11 @@ TEST_P(TimedApplyTest, DISABLED_ChangesBonuses) Bonus testBonus2(BonusDuration::N_TURNS, BonusType::PRIMARY_SKILL, BonusSource::OTHER, 3, BonusSourceID(), BonusSubtypeID(PrimarySkill::KNOWLEDGE)); testBonus2.turnsRemain = 4; - JsonNode options(JsonNode::JsonType::DATA_STRUCT); + JsonNode options; options["cumulative"].Bool() = cumulative; options["bonus"]["test1"] = testBonus1.toJsonNode(); options["bonus"]["test2"] = testBonus2.toJsonNode(); - options.setMeta(ModScope::scopeBuiltin()); + options.setModScope(ModScope::scopeBuiltin()); setupEffect(options); const uint32_t unitId = 42; From d1c274f93f0006c272a2d178f2d2b6250098f1e1 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 13 Feb 2024 15:20:08 +0200 Subject: [PATCH 148/250] Replaced vector of strings with simple bool for flag --- lib/json/JsonNode.cpp | 10 +++++++++ lib/json/JsonNode.h | 19 +++++++++++++---- lib/json/JsonParser.cpp | 28 +++++++++++++++----------- lib/json/JsonUtils.cpp | 2 +- lib/json/JsonWriter.cpp | 4 ---- lib/serializer/ESerializationVersion.h | 5 +++-- 6 files changed, 45 insertions(+), 23 deletions(-) diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index bd41d2549..4d78131ae 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -129,6 +129,16 @@ const std::string & JsonNode::getModScope() const return modScope; } +void JsonNode::setOverrideFlag(bool value) +{ + overrideFlag = value; +} + +bool JsonNode::getOverrideFlag() const +{ + return overrideFlag; +} + void JsonNode::setModScope(const std::string & metadata, bool recursive) { modScope = metadata; diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index 3962316c0..1aa60cfac 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -43,10 +43,9 @@ private: /// Mod-origin of this particular field std::string modScope; -public: - /// meta-flags like override - std::vector flags; + bool overrideFlag = false; +public: JsonNode() = default; /// Create single node with specified value @@ -71,6 +70,9 @@ public: const std::string & getModScope() const; void setModScope(const std::string & metadata, bool recursive = true); + void setOverrideFlag(bool value); + bool getOverrideFlag() const; + /// Convert node to another type. Converting to nullptr will clear all data void setType(JsonType Type); JsonType getType() const; @@ -132,7 +134,16 @@ public: template void serialize(Handler &h) { h & modScope; - h & flags; + + if (h.version >= Handler::Version::JSON_FLAGS) + { + h & overrideFlag; + } + else + { + std::vector oldFlags; + h & oldFlags; + } h & data; } }; diff --git a/lib/json/JsonParser.cpp b/lib/json/JsonParser.cpp index 0d122d53f..b7fb570ec 100644 --- a/lib/json/JsonParser.cpp +++ b/lib/json/JsonParser.cpp @@ -255,20 +255,26 @@ bool JsonParser::extractStruct(JsonNode &node) if (!extractWhitespace()) return false; + bool overrideFlag = false; std::string key; if (!extractString(key)) return false; - // split key string into actual key and meta-flags - std::vector keyAndFlags; - boost::split(keyAndFlags, key, boost::is_any_of("#")); - key = keyAndFlags[0]; - // check for unknown flags - helps with debugging - std::vector knownFlags = { "override" }; - for(int i = 1; i < keyAndFlags.size(); i++) + if (key.find('#') != std::string::npos) { - if(!vstd::contains(knownFlags, keyAndFlags[i])) - error("Encountered unknown flag #" + keyAndFlags[i], true); + // split key string into actual key and meta-flags + std::vector keyAndFlags; + boost::split(keyAndFlags, key, boost::is_any_of("#")); + + key = keyAndFlags[0]; + + for(int i = 1; i < keyAndFlags.size(); i++) + { + if (keyAndFlags[i] == "override") + overrideFlag = true; + else + error("Encountered unknown flag #" + keyAndFlags[i], true); + } } if (node.Struct().find(key) != node.Struct().end()) @@ -280,9 +286,7 @@ bool JsonParser::extractStruct(JsonNode &node) if (!extractElement(node.Struct()[key], '}')) return false; - // flags from key string belong to referenced element - for(int i = 1; i < keyAndFlags.size(); i++) - node.Struct()[key].flags.push_back(keyAndFlags[i]); + node.Struct()[key].setOverrideFlag(overrideFlag); if (input[pos] == '}') { diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 1c9b2be20..5acc78b26 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -223,7 +223,7 @@ void JsonUtils::merge(JsonNode & dest, JsonNode & source, bool ignoreOverride, b } case JsonNode::JsonType::DATA_STRUCT: { - if(!ignoreOverride && vstd::contains(source.flags, "override")) + if(!ignoreOverride && source.getOverrideFlag()) { std::swap(dest, source); } diff --git a/lib/json/JsonWriter.cpp b/lib/json/JsonWriter.cpp index f777a18b4..f8b76a00b 100644 --- a/lib/json/JsonWriter.cpp +++ b/lib/json/JsonWriter.cpp @@ -39,8 +39,6 @@ void JsonWriter::writeEntry(JsonMap::const_iterator entry) { if (!entry->second.getModScope().empty()) out << prefix << " // " << entry->second.getModScope() << "\n"; - if(!entry->second.flags.empty()) - out << prefix << " // flags: " << boost::algorithm::join(entry->second.flags, ", ") << "\n"; out << prefix; } writeString(entry->first); @@ -54,8 +52,6 @@ void JsonWriter::writeEntry(JsonVector::const_iterator entry) { if (!entry->getModScope().empty()) out << prefix << " // " << entry->getModScope() << "\n"; - if(!entry->flags.empty()) - out << prefix << " // flags: " << boost::algorithm::join(entry->flags, ", ") << "\n"; out << prefix; } writeNode(*entry); diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index febc11b44..12436b1c3 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -35,7 +35,8 @@ enum class ESerializationVersion : int32_t RELEASE_143, // 832 +text container in campaigns, +starting hero in RMG options HAS_EXTRA_OPTIONS, // 833 +extra options struct as part of startinfo DESTROYED_OBJECTS, // 834 +list of objects destroyed by player - CAMPAIGN_MAP_TRANSLATIONS, + CAMPAIGN_MAP_TRANSLATIONS, // 835 +campaigns include translations for its maps + JSON_FLAGS, // 836 json uses new format for flags - CURRENT = CAMPAIGN_MAP_TRANSLATIONS + CURRENT = JSON_FLAGS }; From 41493d6f67dd3fb6e08ee734977fb6c85d4b844a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 13 Feb 2024 18:08:35 +0200 Subject: [PATCH 149/250] Implemented 'strict' json support --- lib/CMakeLists.txt | 1 + lib/json/JsonBonus.h | 5 +++++ lib/json/JsonFormatException.h | 20 ++++++++++++++++++++ lib/json/JsonNode.cpp | 12 ++++++++---- lib/json/JsonNode.h | 22 ++++++++++++++++++---- lib/json/JsonParser.cpp | 16 ++++++++++++---- lib/json/JsonParser.h | 4 +++- lib/spells/effects/Moat.h | 2 ++ 8 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 lib/json/JsonFormatException.h diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 68838759d..566c2fdac 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -405,6 +405,7 @@ set(lib_HEADERS filesystem/ResourcePath.h json/JsonBonus.h + json/JsonFormatException.h json/JsonNode.h json/JsonParser.h json/JsonRandom.h diff --git a/lib/json/JsonBonus.h b/lib/json/JsonBonus.h index c994d63e5..dd2b8fa12 100644 --- a/lib/json/JsonBonus.h +++ b/lib/json/JsonBonus.h @@ -14,6 +14,11 @@ VCMI_LIB_NAMESPACE_BEGIN +struct Bonus; +class ILimiter; +class CSelector; +class CAddInfo; + namespace JsonUtils { DLL_LINKAGE std::shared_ptr parseBonus(const JsonVector & ability_vec); diff --git a/lib/json/JsonFormatException.h b/lib/json/JsonFormatException.h new file mode 100644 index 000000000..2d385029d --- /dev/null +++ b/lib/json/JsonFormatException.h @@ -0,0 +1,20 @@ +/* + * JsonFormatException.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 JsonFormatException : public std::runtime_error +{ +public: + using runtime_error::runtime_error; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index 4d78131ae..bb759f72c 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -79,8 +79,12 @@ JsonNode::JsonNode(const std::string & string) {} JsonNode::JsonNode(const std::byte *data, size_t datasize) + :JsonNode(data, datasize, JsonParsingSettings()) +{} + +JsonNode::JsonNode(const std::byte *data, size_t datasize, const JsonParsingSettings & parserSettings) { - JsonParser parser(reinterpret_cast(data), datasize); + JsonParser parser(reinterpret_cast(data), datasize, parserSettings); *this = parser.parse(""); } @@ -88,7 +92,7 @@ JsonNode::JsonNode(const JsonPath & fileURI) { auto file = CResourceHandler::get()->load(fileURI)->readAll(); - JsonParser parser(reinterpret_cast(file.first.get()), file.second); + JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); *this = parser.parse(fileURI.getName()); } @@ -96,7 +100,7 @@ JsonNode::JsonNode(const JsonPath & fileURI, const std::string & idx) { auto file = CResourceHandler::get(idx)->load(fileURI)->readAll(); - JsonParser parser(reinterpret_cast(file.first.get()), file.second); + JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); *this = parser.parse(fileURI.getName()); } @@ -104,7 +108,7 @@ JsonNode::JsonNode(const JsonPath & fileURI, bool &isValidSyntax) { auto file = CResourceHandler::get()->load(fileURI)->readAll(); - JsonParser parser(reinterpret_cast(file.first.get()), file.second); + JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); *this = parser.parse(fileURI.getName()); isValidSyntax = parser.isValid(); } diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index 1aa60cfac..e4e3d787f 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -17,10 +17,23 @@ class JsonNode; using JsonMap = std::map; using JsonVector = std::vector; -struct Bonus; -class CSelector; -class CAddInfo; -class ILimiter; +struct JsonParsingSettings +{ + enum class JsonFormatMode + { + JSON, // strict implementation of json format + JSONC, // json format that also allows comments that start from '//' + //JSON5 // TODO? + }; + + JsonFormatMode mode = JsonFormatMode::JSONC; + + /// Maximum depth of elements + uint32_t maxDepth = 30; + + /// If set to true, parser will throw on any encountered error + bool strict = false; +}; class DLL_LINKAGE JsonNode { @@ -58,6 +71,7 @@ public: /// Create tree from Json-formatted input explicit JsonNode(const std::byte * data, size_t datasize); + explicit JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings); /// Create tree from JSON file explicit JsonNode(const JsonPath & fileURI); diff --git a/lib/json/JsonParser.cpp b/lib/json/JsonParser.cpp index b7fb570ec..2fb31725c 100644 --- a/lib/json/JsonParser.cpp +++ b/lib/json/JsonParser.cpp @@ -11,12 +11,14 @@ #include "StdInc.h" #include "JsonParser.h" +#include "JsonFormatException.h" #include "../TextOperations.h" VCMI_LIB_NAMESPACE_BEGIN -JsonParser::JsonParser(const char * inputString, size_t stringSize): +JsonParser::JsonParser(const char * inputString, size_t stringSize, const JsonParsingSettings & settings): input(inputString, stringSize), + settings(settings), lineCount(1), lineStart(0), pos(0) @@ -105,9 +107,13 @@ bool JsonParser::extractWhitespace(bool verbose) } pos++; } + if (pos >= input.size() || input[pos] != '/') break; + if (settings.mode == JsonParsingSettings::JsonFormatMode::JSON) + error("Comments are not permitted in json!", true); + pos++; if (pos == input.size()) break; @@ -346,9 +352,8 @@ bool JsonParser::extractElement(JsonNode &node, char terminator) if (input[pos] == terminator) { - //FIXME: MOD COMPATIBILITY: Too many of these right now, re-enable later - //if (comma) - //error("Extra comma found!", true); + if (comma) + error("Extra comma found!", true); return true; } @@ -456,6 +461,9 @@ bool JsonParser::extractFloat(JsonNode &node) bool JsonParser::error(const std::string &message, bool warning) { + if (settings.strict) + throw JsonFormatException(message); + std::ostringstream stream; std::string type(warning?" warning: ":" error: "); diff --git a/lib/json/JsonParser.h b/lib/json/JsonParser.h index 5ab544e41..8bae22450 100644 --- a/lib/json/JsonParser.h +++ b/lib/json/JsonParser.h @@ -16,6 +16,8 @@ VCMI_LIB_NAMESPACE_BEGIN //Internal class for string -> JsonNode conversion class JsonParser { + const JsonParsingSettings settings; + std::string errors; // Contains description of all encountered errors std::string_view input; // Input data ui32 lineCount; // Currently parsed line, starting from 1 @@ -44,7 +46,7 @@ class JsonParser bool error(const std::string &message, bool warning=false); public: - JsonParser(const char * inputString, size_t stringSize); + JsonParser(const char * inputString, size_t stringSize, const JsonParsingSettings & settings); /// do actual parsing. filename is name of file that will printed to console if any errors were found JsonNode parse(const std::string & fileName); diff --git a/lib/spells/effects/Moat.h b/lib/spells/effects/Moat.h index 536cf4bfd..c30130743 100644 --- a/lib/spells/effects/Moat.h +++ b/lib/spells/effects/Moat.h @@ -14,6 +14,8 @@ VCMI_LIB_NAMESPACE_BEGIN +struct Bonus; + namespace spells { namespace effects From 2632ab04f53e92e5f404529c4996d73eb6d12c7d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 13 Feb 2024 19:07:51 +0200 Subject: [PATCH 150/250] Partial support for json5 --- lib/json/JsonNode.h | 4 +- lib/json/JsonParser.cpp | 126 +++++++++++++++++++++++++++++++++------- lib/json/JsonParser.h | 6 +- 3 files changed, 111 insertions(+), 25 deletions(-) diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index e4e3d787f..e8005ab60 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -23,10 +23,10 @@ struct JsonParsingSettings { JSON, // strict implementation of json format JSONC, // json format that also allows comments that start from '//' - //JSON5 // TODO? + JSON5 // Partial support of 'json5' format }; - JsonFormatMode mode = JsonFormatMode::JSONC; + JsonFormatMode mode = JsonFormatMode::JSON5; /// Maximum depth of elements uint32_t maxDepth = 30; diff --git a/lib/json/JsonParser.cpp b/lib/json/JsonParser.cpp index 2fb31725c..fc1fd85b3 100644 --- a/lib/json/JsonParser.cpp +++ b/lib/json/JsonParser.cpp @@ -19,6 +19,7 @@ VCMI_LIB_NAMESPACE_BEGIN JsonParser::JsonParser(const char * inputString, size_t stringSize, const JsonParsingSettings & settings): input(inputString, stringSize), settings(settings), + currentDepth(0), lineCount(1), lineStart(0), pos(0) @@ -29,13 +30,13 @@ JsonNode JsonParser::parse(const std::string & fileName) { JsonNode root; - if (input.size() == 0) + if (input.empty()) { error("File is empty", false); } else { - if (!TextOperations::isValidUnicodeString(&input[0], input.size())) + if (!TextOperations::isValidUnicodeString(input.data(), input.size())) error("Not a valid UTF-8 file", false); extractValue(root); @@ -96,6 +97,9 @@ bool JsonParser::extractValue(JsonNode &node) bool JsonParser::extractWhitespace(bool verbose) { + //TODO: JSON5 - C-style multi-line comments + //TODO: JSON5 - Additional white space characters are allowed + while (true) { while(pos < input.size() && static_cast(input[pos]) <= ' ') @@ -133,6 +137,9 @@ bool JsonParser::extractWhitespace(bool verbose) bool JsonParser::extractEscaping(std::string &str) { + // TODO: support unicode escaping: + // \u1234 + switch(input[pos]) { break; case '\"': str += '\"'; @@ -150,15 +157,27 @@ bool JsonParser::extractEscaping(std::string &str) bool JsonParser::extractString(std::string &str) { - if (input[pos] != '\"') - return error("String expected!"); + //TODO: JSON5 - line breaks escaping + + if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + { + if (input[pos] != '\"') + return error("String expected!"); + } + else + { + if (input[pos] != '\"' && input[pos] != '\'') + return error("String expected!"); + } + + char lineTerminator = input[pos]; pos++; size_t first = pos; while (pos != input.size()) { - if (input[pos] == '\"') // Correct end of string + if (input[pos] == lineTerminator) // Correct end of string { str.append( &input[first], pos-first); pos++; @@ -200,23 +219,43 @@ bool JsonParser::extractString(JsonNode &node) return true; } -bool JsonParser::extractLiteral(const std::string &literal) +bool JsonParser::extractLiteral(std::string & literal) { - if (literal.compare(0, literal.size(), &input[pos], literal.size()) != 0) + while (pos < input.size() ) { - while (pos < input.size() && ((input[pos]>'a' && input[pos]<'z') - || (input[pos]>'A' && input[pos]<'Z'))) - pos++; - return error("Unknown literal found", true); + bool isUpperCase = input[pos]>='A' && input[pos]<='Z'; + bool isLowerCase = input[pos]>='a' && input[pos]<='z'; + bool isNumber = input[pos]>='0' && input[pos]<='9'; + + if (!isUpperCase && !isLowerCase && !isNumber) + break; + + literal += input[pos]; + pos++; } pos += literal.size(); return true; } +bool JsonParser::extractAndCompareLiteral(const std::string &expectedLiteral) +{ + std::string literal; + if (!extractLiteral(literal)) + return false; + + if (literal != expectedLiteral) + { + return error("Expected " + expectedLiteral + ", but unknown literal found", true); + return false; + } + + return true; +} + bool JsonParser::extractNull(JsonNode &node) { - if (!extractLiteral("null")) + if (!extractAndCompareLiteral("null")) return false; node.clear(); @@ -225,7 +264,7 @@ bool JsonParser::extractNull(JsonNode &node) bool JsonParser::extractTrue(JsonNode &node) { - if (!extractLiteral("true")) + if (!extractAndCompareLiteral("true")) return false; node.Bool() = true; @@ -234,7 +273,7 @@ bool JsonParser::extractTrue(JsonNode &node) bool JsonParser::extractFalse(JsonNode &node) { - if (!extractLiteral("false")) + if (!extractAndCompareLiteral("false")) return false; node.Bool() = false; @@ -244,6 +283,11 @@ bool JsonParser::extractFalse(JsonNode &node) bool JsonParser::extractStruct(JsonNode &node) { node.setType(JsonNode::JsonType::DATA_STRUCT); + + if (currentDepth > settings.maxDepth) + error("Macimum allowed depth of json structure has been reached", true); + + currentDepth++; pos++; if (!extractWhitespace()) @@ -263,8 +307,25 @@ bool JsonParser::extractStruct(JsonNode &node) bool overrideFlag = false; std::string key; - if (!extractString(key)) - return false; + + if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + { + if (!extractString(key)) + return false; + } + else + { + if (input[pos] == '\'' || input[pos] == '\"') + { + if (!extractString(key)) + return false; + } + else + { + if (!extractLiteral(key)) + return false; + } + } if (key.find('#') != std::string::npos) { @@ -304,6 +365,10 @@ bool JsonParser::extractStruct(JsonNode &node) bool JsonParser::extractArray(JsonNode &node) { + if (currentDepth > settings.maxDepth) + error("Macimum allowed depth of json structure has been reached", true); + + currentDepth++; pos++; node.setType(JsonNode::JsonType::DATA_VECTOR); @@ -353,7 +418,10 @@ bool JsonParser::extractElement(JsonNode &node, char terminator) if (input[pos] == terminator) { if (comma) - error("Extra comma found!", true); + { + if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + error("Extra comma found!", true); + } return true; } @@ -365,20 +433,32 @@ bool JsonParser::extractElement(JsonNode &node, char terminator) bool JsonParser::extractFloat(JsonNode &node) { + //TODO: JSON5 - hexacedimal support + //TODO: JSON5 - Numbers may be IEEE 754 positive infinity, negative infinity, and NaN (why?) + assert(input[pos] == '-' || (input[pos] >= '0' && input[pos] <= '9')); bool negative=false; double result=0; si64 integerPart = 0; bool isFloat = false; - if (input[pos] == '-') + if (input[pos] == '+') + { + if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + error("Positive numbers should not have plus sign!", true); + pos++; + } + else if (input[pos] == '-') { pos++; negative = true; } if (input[pos] < '0' || input[pos] > '9') - return error("Number expected!"); + { + if (input[pos] != '.' && settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + return error("Number expected!"); + } //Extract integer part while (input[pos] >= '0' && input[pos] <= '9') @@ -395,8 +475,12 @@ bool JsonParser::extractFloat(JsonNode &node) isFloat = true; pos++; double fractMult = 0.1; - if (input[pos] < '0' || input[pos] > '9') - return error("Decimal part expected!"); + + if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + { + if (input[pos] < '0' || input[pos] > '9') + return error("Decimal part expected!"); + } while (input[pos] >= '0' && input[pos] <= '9') { diff --git a/lib/json/JsonParser.h b/lib/json/JsonParser.h index 8bae22450..5e2f04b59 100644 --- a/lib/json/JsonParser.h +++ b/lib/json/JsonParser.h @@ -20,13 +20,15 @@ class JsonParser std::string errors; // Contains description of all encountered errors std::string_view input; // Input data - ui32 lineCount; // Currently parsed line, starting from 1 + uint32_t lineCount; // Currently parsed line, starting from 1 + uint32_t currentDepth; size_t lineStart; // Position of current line start size_t pos; // Current position of parser //Helpers bool extractEscaping(std::string &str); - bool extractLiteral(const std::string &literal); + bool extractLiteral(std::string &literal); + bool extractAndCompareLiteral(const std::string &expectedLiteral); bool extractString(std::string &string); bool extractWhitespace(bool verbose = true); bool extractSeparator(); From 18bbccd167ab8a1090e3818987b9de5fd771f73c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 13 Feb 2024 19:16:35 +0200 Subject: [PATCH 151/250] Unify formatting --- lib/json/JsonNode.cpp | 171 +++++++++++++----------- lib/json/JsonNode.h | 22 ++-- lib/json/JsonParser.cpp | 285 ++++++++++++++++++++++------------------ lib/json/JsonParser.h | 36 ++--- lib/json/JsonRandom.h | 2 +- lib/json/JsonWriter.cpp | 59 +++++---- lib/json/JsonWriter.h | 1 + 7 files changed, 315 insertions(+), 261 deletions(-) diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index bb759f72c..7f43d71d9 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -11,9 +11,9 @@ #include "StdInc.h" #include "JsonNode.h" -#include "filesystem/Filesystem.h" #include "JsonParser.h" #include "JsonWriter.h" +#include "filesystem/Filesystem.h" namespace { @@ -40,7 +40,7 @@ Node & resolvePointer(Node & in, const std::string & pointer) auto index = boost::lexical_cast(entry); - if (in.Vector().size() > index) + if(in.Vector().size() > index) return in.Vector()[index].resolvePointer(remainer); } return in[entry].resolvePointer(remainer); @@ -55,36 +55,43 @@ class CModHandler; static const JsonNode nullNode; JsonNode::JsonNode(bool boolean) - :data(boolean) -{} + : data(boolean) +{ +} JsonNode::JsonNode(int32_t number) - :data(static_cast(number)) -{} + : data(static_cast(number)) +{ +} JsonNode::JsonNode(uint32_t number) - :data(static_cast(number)) -{} + : data(static_cast(number)) +{ +} JsonNode::JsonNode(int64_t number) - :data(number) -{} + : data(number) +{ +} JsonNode::JsonNode(double number) - :data(number) -{} + : data(number) +{ +} JsonNode::JsonNode(const std::string & string) - :data(string) -{} - -JsonNode::JsonNode(const std::byte *data, size_t datasize) - :JsonNode(data, datasize, JsonParsingSettings()) -{} - -JsonNode::JsonNode(const std::byte *data, size_t datasize, const JsonParsingSettings & parserSettings) + : data(string) { - JsonParser parser(reinterpret_cast(data), datasize, parserSettings); +} + +JsonNode::JsonNode(const std::byte * data, size_t datasize) + : JsonNode(data, datasize, JsonParsingSettings()) +{ +} + +JsonNode::JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings) +{ + JsonParser parser(reinterpret_cast(data), datasize, parserSettings); *this = parser.parse(""); } @@ -92,33 +99,33 @@ JsonNode::JsonNode(const JsonPath & fileURI) { auto file = CResourceHandler::get()->load(fileURI)->readAll(); - JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); + JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); *this = parser.parse(fileURI.getName()); } JsonNode::JsonNode(const JsonPath & fileURI, const std::string & idx) { auto file = CResourceHandler::get(idx)->load(fileURI)->readAll(); - - JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); + + JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); *this = parser.parse(fileURI.getName()); } -JsonNode::JsonNode(const JsonPath & fileURI, bool &isValidSyntax) +JsonNode::JsonNode(const JsonPath & fileURI, bool & isValidSyntax) { auto file = CResourceHandler::get()->load(fileURI)->readAll(); - JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); + JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); *this = parser.parse(fileURI.getName()); isValidSyntax = parser.isValid(); } -bool JsonNode::operator == (const JsonNode &other) const +bool JsonNode::operator==(const JsonNode & other) const { return data == other.data; } -bool JsonNode::operator != (const JsonNode &other) const +bool JsonNode::operator!=(const JsonNode & other) const { return !(*this == other); } @@ -146,18 +153,20 @@ bool JsonNode::getOverrideFlag() const void JsonNode::setModScope(const std::string & metadata, bool recursive) { modScope = metadata; - if (recursive) + if(recursive) { - switch (getType()) + switch(getType()) { - break; case JsonType::DATA_VECTOR: + break; + case JsonType::DATA_VECTOR: { for(auto & node : Vector()) { node.setModScope(metadata); } } - break; case JsonType::DATA_STRUCT: + break; + case JsonType::DATA_STRUCT: { for(auto & node : Struct()) { @@ -170,7 +179,7 @@ void JsonNode::setModScope(const std::string & metadata, bool recursive) void JsonNode::setType(JsonType Type) { - if (getType() == Type) + if(getType() == Type) return; //float<->int conversion @@ -190,13 +199,27 @@ void JsonNode::setType(JsonType Type) //Set new node type switch(Type) { - break; case JsonType::DATA_NULL: data = JsonData(); - break; case JsonType::DATA_BOOL: data = JsonData(false); - break; case JsonType::DATA_FLOAT: data = JsonData(static_cast(0.0)); - break; case JsonType::DATA_STRING: data = JsonData(std::string()); - break; case JsonType::DATA_VECTOR: data = JsonData(JsonVector()); - break; case JsonType::DATA_STRUCT: data = JsonData(JsonMap()); - break; case JsonType::DATA_INTEGER: data = JsonData(static_cast(0)); + case JsonType::DATA_NULL: + data = JsonData(); + break; + case JsonType::DATA_BOOL: + data = JsonData(false); + break; + case JsonType::DATA_FLOAT: + data = JsonData(static_cast(0.0)); + break; + case JsonType::DATA_STRING: + data = JsonData(std::string()); + break; + case JsonType::DATA_VECTOR: + data = JsonData(JsonVector()); + break; + case JsonType::DATA_STRUCT: + data = JsonData(JsonMap()); + break; + case JsonType::DATA_INTEGER: + data = JsonData(static_cast(0)); + break; } } @@ -229,18 +252,18 @@ bool JsonNode::containsBaseData() const { switch(getType()) { - case JsonType::DATA_NULL: - return false; - case JsonType::DATA_STRUCT: - for(const auto & elem : Struct()) - { - if(elem.second.containsBaseData()) - return true; - } - return false; - default: - //other types (including vector) cannot be extended via merge - return true; + case JsonType::DATA_NULL: + return false; + case JsonType::DATA_STRUCT: + for(const auto & elem : Struct()) + { + if(elem.second.containsBaseData()) + return true; + } + return false; + default: + //other types (including vector) cannot be extended via merge + return true; } } @@ -248,14 +271,14 @@ bool JsonNode::isCompact() const { switch(getType()) { - case JsonType::DATA_VECTOR: - for(const JsonNode & elem : Vector()) - { - if(!elem.isCompact()) - return false; - } - return true; - case JsonType::DATA_STRUCT: + case JsonType::DATA_VECTOR: + for(const JsonNode & elem : Vector()) + { + if(!elem.isCompact()) + return false; + } + return true; + case JsonType::DATA_STRUCT: { auto propertyCount = Struct().size(); if(propertyCount == 0) @@ -263,9 +286,9 @@ bool JsonNode::isCompact() const else if(propertyCount == 1) return Struct().begin()->second.isCompact(); } - return false; - default: - return true; + return false; + default: + return true; } } @@ -285,7 +308,7 @@ bool JsonNode::TryBoolFromString(bool & success) const if(success) return true; - + success = boolParamStr == "false"; } return false; @@ -337,7 +360,7 @@ bool JsonNode::Bool() const { assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_BOOL); - if (getType() == JsonType::DATA_BOOL) + if(getType() == JsonType::DATA_BOOL) return std::get(data); return boolDefault; @@ -376,7 +399,7 @@ const std::string & JsonNode::String() const { assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRING); - if (getType() == JsonType::DATA_STRING) + if(getType() == JsonType::DATA_STRING) return std::get(data); return stringDefault; @@ -387,7 +410,7 @@ const JsonVector & JsonNode::Vector() const { assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_VECTOR); - if (getType() == JsonType::DATA_VECTOR) + if(getType() == JsonType::DATA_VECTOR) return std::get(data); return vectorDefault; @@ -398,7 +421,7 @@ const JsonMap & JsonNode::Struct() const { assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRUCT); - if (getType() == JsonType::DATA_STRUCT) + if(getType() == JsonType::DATA_STRUCT) return std::get(data); return mapDefault; @@ -412,32 +435,32 @@ JsonNode & JsonNode::operator[](const std::string & child) const JsonNode & JsonNode::operator[](const std::string & child) const { auto it = Struct().find(child); - if (it != Struct().end()) + if(it != Struct().end()) return it->second; return nullNode; } JsonNode & JsonNode::operator[](size_t child) { - if (child >= Vector().size() ) + if(child >= Vector().size()) Vector().resize(child + 1); return Vector()[child]; } const JsonNode & JsonNode::operator[](size_t child) const { - if (child < Vector().size() ) + if(child < Vector().size()) return Vector()[child]; return nullNode; } -const JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) const +const JsonNode & JsonNode::resolvePointer(const std::string & jsonPointer) const { return ::resolvePointer(*this, jsonPointer); } -JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) +JsonNode & JsonNode::resolvePointer(const std::string & jsonPointer) { return ::resolvePointer(*this, jsonPointer); } @@ -445,7 +468,7 @@ JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) std::vector JsonNode::toBytes() const { std::string jsonString = toString(); - auto dataBegin = reinterpret_cast(jsonString.data()); + auto dataBegin = reinterpret_cast(jsonString.data()); auto dataEnd = dataBegin + jsonString.size(); std::vector result(dataBegin, dataEnd); return result; diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index e8005ab60..97a3350b3 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -58,6 +58,7 @@ private: std::string modScope; bool overrideFlag = false; + public: JsonNode() = default; @@ -78,8 +79,8 @@ public: explicit JsonNode(const JsonPath & fileURI, const std::string & modName); explicit JsonNode(const JsonPath & fileURI, bool & isValidSyntax); - bool operator == (const JsonNode &other) const; - bool operator != (const JsonNode &other) const; + bool operator==(const JsonNode & other) const; + bool operator!=(const JsonNode & other) const; const std::string & getModScope() const; void setModScope(const std::string & metadata, bool recursive = true); @@ -139,17 +140,18 @@ public: const JsonNode & operator[](const std::string & child) const; JsonNode & operator[](size_t child); - const JsonNode & operator[](size_t child) const; + const JsonNode & operator[](size_t child) const; std::string toCompactString() const; std::string toString() const; std::vector toBytes() const; - template void serialize(Handler &h) + template + void serialize(Handler & h) { h & modScope; - if (h.version >= Handler::Version::JSON_FLAGS) + if(h.version >= Handler::Version::JSON_FLAGS) { h & overrideFlag; } @@ -187,15 +189,15 @@ inline void convert(std::string & value, const JsonNode & node) value = node.String(); } -template -void convert(std::map & value, const JsonNode & node) +template +void convert(std::map & value, const JsonNode & node) { value.clear(); - for (const JsonMap::value_type & entry : node.Struct()) + for(const JsonMap::value_type & entry : node.Struct()) value.insert(entry.first, entry.second.convertTo()); } -template +template void convert(std::set & value, const JsonNode & node) { value.clear(); @@ -205,7 +207,7 @@ void convert(std::set & value, const JsonNode & node) } } -template +template void convert(std::vector & value, const JsonNode & node) { value.clear(); diff --git a/lib/json/JsonParser.cpp b/lib/json/JsonParser.cpp index fc1fd85b3..8f7b3af45 100644 --- a/lib/json/JsonParser.cpp +++ b/lib/json/JsonParser.cpp @@ -11,18 +11,18 @@ #include "StdInc.h" #include "JsonParser.h" -#include "JsonFormatException.h" #include "../TextOperations.h" +#include "JsonFormatException.h" VCMI_LIB_NAMESPACE_BEGIN -JsonParser::JsonParser(const char * inputString, size_t stringSize, const JsonParsingSettings & settings): - input(inputString, stringSize), - settings(settings), - currentDepth(0), - lineCount(1), - lineStart(0), - pos(0) +JsonParser::JsonParser(const char * inputString, size_t stringSize, const JsonParsingSettings & settings) + : input(inputString, stringSize) + , settings(settings) + , currentDepth(0) + , lineCount(1) + , lineStart(0) + , pos(0) { } @@ -30,24 +30,24 @@ JsonNode JsonParser::parse(const std::string & fileName) { JsonNode root; - if (input.empty()) + if(input.empty()) { error("File is empty", false); } else { - if (!TextOperations::isValidUnicodeString(input.data(), input.size())) + if(!TextOperations::isValidUnicodeString(input.data(), input.size())) error("Not a valid UTF-8 file", false); extractValue(root); extractWhitespace(false); //Warn if there are any non-whitespace symbols left - if (pos < input.size()) + if(pos < input.size()) error("Not all file was parsed!", true); } - if (!errors.empty()) + if(!errors.empty()) { logMod->warn("File %s is not a valid JSON file!", fileName); logMod->warn(errors); @@ -62,33 +62,40 @@ bool JsonParser::isValid() bool JsonParser::extractSeparator() { - if (!extractWhitespace()) + if(!extractWhitespace()) return false; - if ( input[pos] !=':') + if(input[pos] != ':') return error("Separator expected"); pos++; return true; } -bool JsonParser::extractValue(JsonNode &node) +bool JsonParser::extractValue(JsonNode & node) { - if (!extractWhitespace()) + if(!extractWhitespace()) return false; - switch (input[pos]) + switch(input[pos]) { - case '\"': return extractString(node); - case 'n' : return extractNull(node); - case 't' : return extractTrue(node); - case 'f' : return extractFalse(node); - case '{' : return extractStruct(node); - case '[' : return extractArray(node); - case '-' : return extractFloat(node); + case '\"': + return extractString(node); + case 'n': + return extractNull(node); + case 't': + return extractTrue(node); + case 'f': + return extractFalse(node); + case '{': + return extractStruct(node); + case '[': + return extractArray(node); + case '-': + return extractFloat(node); default: { - if (input[pos] >= '0' && input[pos] <= '9') + if(input[pos] >= '0' && input[pos] <= '9') return extractFloat(node); return error("Value expected!"); } @@ -100,73 +107,90 @@ bool JsonParser::extractWhitespace(bool verbose) //TODO: JSON5 - C-style multi-line comments //TODO: JSON5 - Additional white space characters are allowed - while (true) + while(true) { while(pos < input.size() && static_cast(input[pos]) <= ' ') { - if (input[pos] == '\n') + if(input[pos] == '\n') { lineCount++; - lineStart = pos+1; + lineStart = pos + 1; } pos++; } - if (pos >= input.size() || input[pos] != '/') + if(pos >= input.size() || input[pos] != '/') break; - if (settings.mode == JsonParsingSettings::JsonFormatMode::JSON) + if(settings.mode == JsonParsingSettings::JsonFormatMode::JSON) error("Comments are not permitted in json!", true); pos++; - if (pos == input.size()) + if(pos == input.size()) break; - if (input[pos] == '/') + if(input[pos] == '/') pos++; else error("Comments must consist of two slashes!", true); - while (pos < input.size() && input[pos] != '\n') + while(pos < input.size() && input[pos] != '\n') pos++; } - if (pos >= input.size() && verbose) + if(pos >= input.size() && verbose) return error("Unexpected end of file!"); return true; } -bool JsonParser::extractEscaping(std::string &str) +bool JsonParser::extractEscaping(std::string & str) { // TODO: support unicode escaping: // \u1234 switch(input[pos]) { - break; case '\"': str += '\"'; - break; case '\\': str += '\\'; - break; case 'b': str += '\b'; - break; case 'f': str += '\f'; - break; case 'n': str += '\n'; - break; case 'r': str += '\r'; - break; case 't': str += '\t'; - break; case '/': str += '/'; - break; default: return error("Unknown escape sequence!", true); + case '\"': + str += '\"'; + break; + case '\\': + str += '\\'; + break; + case 'b': + str += '\b'; + break; + case 'f': + str += '\f'; + break; + case 'n': + str += '\n'; + break; + case 'r': + str += '\r'; + break; + case 't': + str += '\t'; + break; + case '/': + str += '/'; + break; + default: + return error("Unknown escape sequence!", true); } return true; } -bool JsonParser::extractString(std::string &str) +bool JsonParser::extractString(std::string & str) { //TODO: JSON5 - line breaks escaping - if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) { - if (input[pos] != '\"') + if(input[pos] != '\"') return error("String expected!"); } else { - if (input[pos] != '\"' && input[pos] != '\'') + if(input[pos] != '\"' && input[pos] != '\'') return error("String expected!"); } @@ -175,32 +199,32 @@ bool JsonParser::extractString(std::string &str) size_t first = pos; - while (pos != input.size()) + while(pos != input.size()) { - if (input[pos] == lineTerminator) // Correct end of string + if(input[pos] == lineTerminator) // Correct end of string { - str.append( &input[first], pos-first); + str.append(&input[first], pos - first); pos++; return true; } - if (input[pos] == '\\') // Escaping + if(input[pos] == '\\') // Escaping { - str.append( &input[first], pos-first); + str.append(&input[first], pos - first); pos++; - if (pos == input.size()) + if(pos == input.size()) break; extractEscaping(str); first = pos + 1; } - if (input[pos] == '\n') // end-of-line + if(input[pos] == '\n') // end-of-line { - str.append( &input[first], pos-first); + str.append(&input[first], pos - first); return error("Closing quote not found!", true); } if(static_cast(input[pos]) < ' ') // control character { - str.append( &input[first], pos-first); - first = pos+1; + str.append(&input[first], pos - first); + first = pos + 1; error("Illegal character in the string!", true); } pos++; @@ -208,10 +232,10 @@ bool JsonParser::extractString(std::string &str) return error("Unterminated string!"); } -bool JsonParser::extractString(JsonNode &node) +bool JsonParser::extractString(JsonNode & node) { std::string str; - if (!extractString(str)) + if(!extractString(str)) return false; node.setType(JsonNode::JsonType::DATA_STRING); @@ -221,13 +245,13 @@ bool JsonParser::extractString(JsonNode &node) bool JsonParser::extractLiteral(std::string & literal) { - while (pos < input.size() ) + while(pos < input.size()) { - bool isUpperCase = input[pos]>='A' && input[pos]<='Z'; - bool isLowerCase = input[pos]>='a' && input[pos]<='z'; - bool isNumber = input[pos]>='0' && input[pos]<='9'; + bool isUpperCase = input[pos] >= 'A' && input[pos] <= 'Z'; + bool isLowerCase = input[pos] >= 'a' && input[pos] <= 'z'; + bool isNumber = input[pos] >= '0' && input[pos] <= '9'; - if (!isUpperCase && !isLowerCase && !isNumber) + if(!isUpperCase && !isLowerCase && !isNumber) break; literal += input[pos]; @@ -238,13 +262,13 @@ bool JsonParser::extractLiteral(std::string & literal) return true; } -bool JsonParser::extractAndCompareLiteral(const std::string &expectedLiteral) +bool JsonParser::extractAndCompareLiteral(const std::string & expectedLiteral) { std::string literal; - if (!extractLiteral(literal)) + if(!extractLiteral(literal)) return false; - if (literal != expectedLiteral) + if(literal != expectedLiteral) { return error("Expected " + expectedLiteral + ", but unknown literal found", true); return false; @@ -253,81 +277,81 @@ bool JsonParser::extractAndCompareLiteral(const std::string &expectedLiteral) return true; } -bool JsonParser::extractNull(JsonNode &node) +bool JsonParser::extractNull(JsonNode & node) { - if (!extractAndCompareLiteral("null")) + if(!extractAndCompareLiteral("null")) return false; node.clear(); return true; } -bool JsonParser::extractTrue(JsonNode &node) +bool JsonParser::extractTrue(JsonNode & node) { - if (!extractAndCompareLiteral("true")) + if(!extractAndCompareLiteral("true")) return false; node.Bool() = true; return true; } -bool JsonParser::extractFalse(JsonNode &node) +bool JsonParser::extractFalse(JsonNode & node) { - if (!extractAndCompareLiteral("false")) + if(!extractAndCompareLiteral("false")) return false; node.Bool() = false; return true; } -bool JsonParser::extractStruct(JsonNode &node) +bool JsonParser::extractStruct(JsonNode & node) { node.setType(JsonNode::JsonType::DATA_STRUCT); - if (currentDepth > settings.maxDepth) + if(currentDepth > settings.maxDepth) error("Macimum allowed depth of json structure has been reached", true); currentDepth++; pos++; - if (!extractWhitespace()) + if(!extractWhitespace()) return false; //Empty struct found - if (input[pos] == '}') + if(input[pos] == '}') { pos++; return true; } - while (true) + while(true) { - if (!extractWhitespace()) + if(!extractWhitespace()) return false; bool overrideFlag = false; std::string key; - if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) { - if (!extractString(key)) + if(!extractString(key)) return false; } else { - if (input[pos] == '\'' || input[pos] == '\"') + if(input[pos] == '\'' || input[pos] == '\"') { - if (!extractString(key)) + if(!extractString(key)) return false; } else { - if (!extractLiteral(key)) + if(!extractLiteral(key)) return false; } } - if (key.find('#') != std::string::npos) + if(key.find('#') != std::string::npos) { // split key string into actual key and meta-flags std::vector keyAndFlags; @@ -337,25 +361,25 @@ bool JsonParser::extractStruct(JsonNode &node) for(int i = 1; i < keyAndFlags.size(); i++) { - if (keyAndFlags[i] == "override") + if(keyAndFlags[i] == "override") overrideFlag = true; else error("Encountered unknown flag #" + keyAndFlags[i], true); } } - if (node.Struct().find(key) != node.Struct().end()) + if(node.Struct().find(key) != node.Struct().end()) error("Duplicate element encountered!", true); - if (!extractSeparator()) + if(!extractSeparator()) return false; - if (!extractElement(node.Struct()[key], '}')) + if(!extractElement(node.Struct()[key], '}')) return false; node.Struct()[key].setOverrideFlag(overrideFlag); - if (input[pos] == '}') + if(input[pos] == '}') { pos++; return true; @@ -363,35 +387,35 @@ bool JsonParser::extractStruct(JsonNode &node) } } -bool JsonParser::extractArray(JsonNode &node) +bool JsonParser::extractArray(JsonNode & node) { - if (currentDepth > settings.maxDepth) + if(currentDepth > settings.maxDepth) error("Macimum allowed depth of json structure has been reached", true); currentDepth++; pos++; node.setType(JsonNode::JsonType::DATA_VECTOR); - if (!extractWhitespace()) + if(!extractWhitespace()) return false; //Empty array found - if (input[pos] == ']') + if(input[pos] == ']') { pos++; return true; } - while (true) + while(true) { //NOTE: currently 50% of time is this vector resizing. //May be useful to use list during parsing and then swap() all items to vector - node.Vector().resize(node.Vector().size()+1); + node.Vector().resize(node.Vector().size() + 1); - if (!extractElement(node.Vector().back(), ']')) + if(!extractElement(node.Vector().back(), ']')) return false; - if (input[pos] == ']') + if(input[pos] == ']') { pos++; return true; @@ -399,92 +423,92 @@ bool JsonParser::extractArray(JsonNode &node) } } -bool JsonParser::extractElement(JsonNode &node, char terminator) +bool JsonParser::extractElement(JsonNode & node, char terminator) { - if (!extractValue(node)) + if(!extractValue(node)) return false; - if (!extractWhitespace()) + if(!extractWhitespace()) return false; bool comma = (input[pos] == ','); - if (comma ) + if(comma) { pos++; - if (!extractWhitespace()) + if(!extractWhitespace()) return false; } - if (input[pos] == terminator) + if(input[pos] == terminator) { - if (comma) + if(comma) { - if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) error("Extra comma found!", true); } return true; } - if (!comma) + if(!comma) error("Comma expected!", true); return true; } -bool JsonParser::extractFloat(JsonNode &node) +bool JsonParser::extractFloat(JsonNode & node) { //TODO: JSON5 - hexacedimal support //TODO: JSON5 - Numbers may be IEEE 754 positive infinity, negative infinity, and NaN (why?) assert(input[pos] == '-' || (input[pos] >= '0' && input[pos] <= '9')); - bool negative=false; - double result=0; + bool negative = false; + double result = 0; si64 integerPart = 0; bool isFloat = false; - if (input[pos] == '+') + if(input[pos] == '+') { - if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) error("Positive numbers should not have plus sign!", true); pos++; } - else if (input[pos] == '-') + else if(input[pos] == '-') { pos++; negative = true; } - if (input[pos] < '0' || input[pos] > '9') + if(input[pos] < '0' || input[pos] > '9') { - if (input[pos] != '.' && settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + if(input[pos] != '.' && settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) return error("Number expected!"); } //Extract integer part - while (input[pos] >= '0' && input[pos] <= '9') + while(input[pos] >= '0' && input[pos] <= '9') { - integerPart = integerPart*10+(input[pos]-'0'); + integerPart = integerPart * 10 + (input[pos] - '0'); pos++; } result = static_cast(integerPart); - if (input[pos] == '.') + if(input[pos] == '.') { //extract fractional part isFloat = true; pos++; double fractMult = 0.1; - if (settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) { - if (input[pos] < '0' || input[pos] > '9') + if(input[pos] < '0' || input[pos] > '9') return error("Decimal part expected!"); } - while (input[pos] >= '0' && input[pos] <= '9') + while(input[pos] >= '0' && input[pos] <= '9') { - result = result + fractMult*(input[pos]-'0'); + result = result + fractMult * (input[pos] - '0'); fractMult /= 10; pos++; } @@ -508,12 +532,12 @@ bool JsonParser::extractFloat(JsonNode &node) pos++; } - if (input[pos] < '0' || input[pos] > '9') + if(input[pos] < '0' || input[pos] > '9') return error("Exponential part expected!"); - while (input[pos] >= '0' && input[pos] <= '9') + while(input[pos] >= '0' && input[pos] <= '9') { - power = power*10 + (input[pos]-'0'); + power = power * 10 + (input[pos] - '0'); pos++; } @@ -543,16 +567,15 @@ bool JsonParser::extractFloat(JsonNode &node) return true; } -bool JsonParser::error(const std::string &message, bool warning) +bool JsonParser::error(const std::string & message, bool warning) { - if (settings.strict) + if(settings.strict) throw JsonFormatException(message); std::ostringstream stream; - std::string type(warning?" warning: ":" error: "); + std::string type(warning ? " warning: " : " error: "); - stream << "At line " << lineCount << ", position "< void JsonWriter::writeContainer(Iterator begin, Iterator end) { - if (begin == end) + if(begin == end) return; prefix += '\t'; writeEntry(begin++); - while (begin != end) + while(begin != end) { out << (compactMode ? ", " : ",\n"); writeEntry(begin++); } out << (compactMode ? "" : "\n"); - prefix.resize(prefix.size()-1); + prefix.resize(prefix.size() - 1); } void JsonWriter::writeEntry(JsonMap::const_iterator entry) { if(!compactMode) { - if (!entry->second.getModScope().empty()) + if(!entry->second.getModScope().empty()) out << prefix << " // " << entry->second.getModScope() << "\n"; out << prefix; } @@ -50,27 +50,25 @@ void JsonWriter::writeEntry(JsonVector::const_iterator entry) { if(!compactMode) { - if (!entry->getModScope().empty()) + if(!entry->getModScope().empty()) out << prefix << " // " << entry->getModScope() << "\n"; out << prefix; } writeNode(*entry); } -void JsonWriter::writeString(const std::string &string) +void JsonWriter::writeString(const std::string & string) { static const std::string escaped = "\"\\\b\f\n\r\t"; static const std::array escaped_code = {'\"', '\\', 'b', 'f', 'n', 'r', 't'}; - out <<'\"'; + out << '\"'; size_t pos = 0; size_t start = 0; - for (; pos void writeContainer(Iterator begin, Iterator end); From 29860848a5cd817f4d85f07c73df37320d760b10 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 13 Feb 2024 22:19:24 +0200 Subject: [PATCH 152/250] Cleanup --- lib/CArtHandler.cpp | 3 +- lib/json/JsonBonus.cpp | 12 ++----- lib/json/JsonNode.cpp | 40 ++++++++++++++---------- lib/json/JsonNode.h | 1 + lib/json/JsonParser.cpp | 38 ++++++++++++---------- lib/json/JsonParser.h | 2 +- lib/json/JsonRandom.cpp | 4 +-- lib/json/JsonUtils.cpp | 11 ------- lib/json/JsonValidator.cpp | 21 +++++++------ lib/json/JsonWriter.cpp | 6 ++-- lib/mapObjects/ObjectTemplate.cpp | 2 +- lib/modding/CModHandler.cpp | 5 ++- test/scripting/LuaSpellEffectAPITest.cpp | 4 +-- test/scripting/LuaSpellEffectTest.cpp | 12 +++---- 14 files changed, 80 insertions(+), 81 deletions(-) diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index f5bac24e5..d18bfe425 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -350,8 +350,7 @@ std::vector CArtHandler::loadLegacyData() { if(parser.readString() == "x") { - artData["slot"].Vector().push_back(JsonNode()); - artData["slot"].Vector().back().String() = artSlot; + artData["slot"].Vector().emplace_back(artSlot); } } artData["class"].String() = classes.at(parser.readString()[0]); diff --git a/lib/json/JsonBonus.cpp b/lib/json/JsonBonus.cpp index 5288560b0..76b5f77ab 100644 --- a/lib/json/JsonBonus.cpp +++ b/lib/json/JsonBonus.cpp @@ -13,23 +13,17 @@ #include "JsonValidator.h" -#include "../ScopeGuard.h" +#include "../CGeneralTextHandler.h" +#include "../VCMI_Lib.h" #include "../bonuses/BonusParams.h" -#include "../bonuses/Bonus.h" #include "../bonuses/Limiters.h" #include "../bonuses/Propagators.h" #include "../bonuses/Updaters.h" -#include "../filesystem/Filesystem.h" -#include "../modding/IdentifierStorage.h" -#include "../VCMI_Lib.h" //for identifier resolution -#include "../CGeneralTextHandler.h" #include "../constants/StringConstants.h" -#include "../battle/BattleHex.h" +#include "../modding/IdentifierStorage.h" VCMI_LIB_NAMESPACE_BEGIN -static const JsonNode nullNode; - static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node) { if (node.isNull()) diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index 7f43d71d9..7ace2fc3f 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -15,8 +15,6 @@ #include "JsonWriter.h" #include "filesystem/Filesystem.h" -namespace -{ // to avoid duplicating const and non-const code template Node & resolvePointer(Node & in, const std::string & pointer) @@ -45,15 +43,14 @@ Node & resolvePointer(Node & in, const std::string & pointer) } return in[entry].resolvePointer(remainer); } -} VCMI_LIB_NAMESPACE_BEGIN +static const JsonNode nullNode; + class LibClasses; class CModHandler; -static const JsonNode nullNode; - JsonNode::JsonNode(bool boolean) : data(boolean) { @@ -91,15 +88,20 @@ JsonNode::JsonNode(const std::byte * data, size_t datasize) JsonNode::JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings) { - JsonParser parser(reinterpret_cast(data), datasize, parserSettings); + JsonParser parser(data, datasize, parserSettings); *this = parser.parse(""); } JsonNode::JsonNode(const JsonPath & fileURI) + :JsonNode(fileURI, JsonParsingSettings()) +{ +} + +JsonNode::JsonNode(const JsonPath & fileURI, const JsonParsingSettings & parserSettings) { auto file = CResourceHandler::get()->load(fileURI)->readAll(); - JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); + JsonParser parser(reinterpret_cast(file.first.get()), file.second, parserSettings); *this = parser.parse(fileURI.getName()); } @@ -107,7 +109,7 @@ JsonNode::JsonNode(const JsonPath & fileURI, const std::string & idx) { auto file = CResourceHandler::get(idx)->load(fileURI)->readAll(); - JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); + JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); *this = parser.parse(fileURI.getName()); } @@ -115,7 +117,7 @@ JsonNode::JsonNode(const JsonPath & fileURI, bool & isValidSyntax) { auto file = CResourceHandler::get()->load(fileURI)->readAll(); - JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); + JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); *this = parser.parse(fileURI.getName()); isValidSyntax = parser.isValid(); } @@ -206,7 +208,7 @@ void JsonNode::setType(JsonType Type) data = JsonData(false); break; case JsonType::DATA_FLOAT: - data = JsonData(static_cast(0.0)); + data = JsonData(0.0); break; case JsonType::DATA_STRING: data = JsonData(std::string()); @@ -355,9 +357,10 @@ JsonMap & JsonNode::Struct() return std::get(data); } -const bool boolDefault = false; bool JsonNode::Bool() const { + static const bool boolDefault = false; + assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_BOOL); if(getType() == JsonType::DATA_BOOL) @@ -366,9 +369,10 @@ bool JsonNode::Bool() const return boolDefault; } -const double floatDefault = 0; double JsonNode::Float() const { + static const double floatDefault = 0; + assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT); if(getType() == JsonType::DATA_FLOAT) @@ -380,9 +384,10 @@ double JsonNode::Float() const return floatDefault; } -const si64 integerDefault = 0; si64 JsonNode::Integer() const { + static const si64 integerDefault = 0; + assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT); if(getType() == JsonType::DATA_INTEGER) @@ -394,9 +399,10 @@ si64 JsonNode::Integer() const return integerDefault; } -const std::string stringDefault = std::string(); const std::string & JsonNode::String() const { + static const std::string stringDefault = std::string(); + assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRING); if(getType() == JsonType::DATA_STRING) @@ -405,9 +411,10 @@ const std::string & JsonNode::String() const return stringDefault; } -const JsonVector vectorDefault = JsonVector(); const JsonVector & JsonNode::Vector() const { + static const JsonVector vectorDefault = JsonVector(); + assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_VECTOR); if(getType() == JsonType::DATA_VECTOR) @@ -416,9 +423,10 @@ const JsonVector & JsonNode::Vector() const return vectorDefault; } -const JsonMap mapDefault = JsonMap(); const JsonMap & JsonNode::Struct() const { + static const JsonMap mapDefault = JsonMap(); + assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRUCT); if(getType() == JsonType::DATA_STRUCT) diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index 97a3350b3..0aff27e89 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -76,6 +76,7 @@ public: /// Create tree from JSON file explicit JsonNode(const JsonPath & fileURI); + explicit JsonNode(const JsonPath & fileURI, const JsonParsingSettings & parserSettings); explicit JsonNode(const JsonPath & fileURI, const std::string & modName); explicit JsonNode(const JsonPath & fileURI, bool & isValidSyntax); diff --git a/lib/json/JsonParser.cpp b/lib/json/JsonParser.cpp index 8f7b3af45..e43c31c22 100644 --- a/lib/json/JsonParser.cpp +++ b/lib/json/JsonParser.cpp @@ -11,16 +11,17 @@ #include "StdInc.h" #include "JsonParser.h" +#include "../ScopeGuard.h" #include "../TextOperations.h" #include "JsonFormatException.h" VCMI_LIB_NAMESPACE_BEGIN -JsonParser::JsonParser(const char * inputString, size_t stringSize, const JsonParsingSettings & settings) - : input(inputString, stringSize) - , settings(settings) - , currentDepth(0) +JsonParser::JsonParser(const std::byte * inputString, size_t stringSize, const JsonParsingSettings & settings) + : settings(settings) + , input(reinterpret_cast(inputString), stringSize) , lineCount(1) + , currentDepth(0) , lineStart(0) , pos(0) { @@ -258,7 +259,6 @@ bool JsonParser::extractLiteral(std::string & literal) pos++; } - pos += literal.size(); return true; } @@ -309,10 +309,14 @@ bool JsonParser::extractStruct(JsonNode & node) node.setType(JsonNode::JsonType::DATA_STRUCT); if(currentDepth > settings.maxDepth) - error("Macimum allowed depth of json structure has been reached", true); + error("Maximum allowed depth of json structure has been reached", true); - currentDepth++; pos++; + currentDepth++; + auto guard = vstd::makeScopeGuard([this]() + { + currentDepth--; + }); if(!extractWhitespace()) return false; @@ -393,6 +397,11 @@ bool JsonParser::extractArray(JsonNode & node) error("Macimum allowed depth of json structure has been reached", true); currentDepth++; + auto guard = vstd::makeScopeGuard([this]() + { + currentDepth--; + }); + pos++; node.setType(JsonNode::JsonType::DATA_VECTOR); @@ -441,11 +450,9 @@ bool JsonParser::extractElement(JsonNode & node, char terminator) if(input[pos] == terminator) { - if(comma) - { - if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) - error("Extra comma found!", true); - } + if(comma && settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) + error("Extra comma found!", true); + return true; } @@ -500,11 +507,8 @@ bool JsonParser::extractFloat(JsonNode & node) pos++; double fractMult = 0.1; - if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) - { - if(input[pos] < '0' || input[pos] > '9') - return error("Decimal part expected!"); - } + if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5 && (input[pos] < '0' || input[pos] > '9')) + return error("Decimal part expected!"); while(input[pos] >= '0' && input[pos] <= '9') { diff --git a/lib/json/JsonParser.h b/lib/json/JsonParser.h index d4ccb529e..8bf3259c7 100644 --- a/lib/json/JsonParser.h +++ b/lib/json/JsonParser.h @@ -48,7 +48,7 @@ class JsonParser bool error(const std::string & message, bool warning = false); public: - JsonParser(const char * inputString, size_t stringSize, const JsonParsingSettings & settings); + JsonParser(const std::byte * inputString, size_t stringSize, const JsonParsingSettings & settings); /// do actual parsing. filename is name of file that will printed to console if any errors were found JsonNode parse(const std::string & fileName); diff --git a/lib/json/JsonRandom.cpp b/lib/json/JsonRandom.cpp index 1c5234ac8..df71283b2 100644 --- a/lib/json/JsonRandom.cpp +++ b/lib/json/JsonRandom.cpp @@ -235,9 +235,9 @@ VCMI_LIB_NAMESPACE_BEGIN filteredAnyOf.insert(subset.begin(), subset.end()); } - vstd::erase_if(filteredTypes, [&](const IdentifierType & value) + vstd::erase_if(filteredTypes, [&filteredAnyOf](const IdentifierType & filteredValue) { - return filteredAnyOf.count(value) == 0; + return filteredAnyOf.count(filteredValue) == 0; }); } diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 5acc78b26..69851c6ca 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -13,18 +13,7 @@ #include "JsonValidator.h" -#include "../ScopeGuard.h" -#include "../bonuses/BonusParams.h" -#include "../bonuses/Bonus.h" -#include "../bonuses/Limiters.h" -#include "../bonuses/Propagators.h" -#include "../bonuses/Updaters.h" #include "../filesystem/Filesystem.h" -#include "../modding/IdentifierStorage.h" -#include "../VCMI_Lib.h" //for identifier resolution -#include "../CGeneralTextHandler.h" -#include "../constants/StringConstants.h" -#include "../battle/BattleHex.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/json/JsonValidator.cpp b/lib/json/JsonValidator.cpp index b4090af82..6c1cff192 100644 --- a/lib/json/JsonValidator.cpp +++ b/lib/json/JsonValidator.cpp @@ -81,7 +81,7 @@ namespace std::string allOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) { - return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass all schemas", [&](size_t count) + return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass all schemas", [&schema](size_t count) { return count == schema.Vector().size(); }); @@ -89,7 +89,7 @@ namespace std::string anyOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) { - return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass any schema", [&](size_t count) + return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass any schema", [](size_t count) { return count > 0; }); @@ -97,7 +97,7 @@ namespace std::string oneOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) { - return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass exactly one schema", [&](size_t count) + return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass exactly one schema", [](size_t count) { return count == 1; }); @@ -228,7 +228,7 @@ namespace std::string multipleOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) { - double result = data.Float() / schema.Float(); + double result = data.Integer() / schema.Integer(); if (!vstd::isAlmostEqual(floor(result), result)) return validator.makeErrorMessage((boost::format("Value is not divisible by %d") % schema.Float()).str()); return ""; @@ -241,7 +241,7 @@ namespace { validator.currentPath.emplace_back(); validator.currentPath.back().Float() = static_cast(index); - auto onExit = vstd::makeScopeGuard([&]() + auto onExit = vstd::makeScopeGuard([&validator]() { validator.currentPath.pop_back(); }); @@ -390,7 +390,7 @@ namespace { validator.currentPath.emplace_back(); validator.currentPath.back().String() = nodeName; - auto onExit = vstd::makeScopeGuard([&]() + auto onExit = vstd::makeScopeGuard([&validator]() { validator.currentPath.pop_back(); }); @@ -531,6 +531,9 @@ namespace ret["title"] = Common::emptyCheck; ret["$schema"] = Common::emptyCheck; ret["default"] = Common::emptyCheck; + ret["defaultIOS"] = Common::emptyCheck; + ret["defaultAndroid"] = Common::emptyCheck; + ret["defaultWindows"] = Common::emptyCheck; ret["description"] = Common::emptyCheck; ret["definitions"] = Common::emptyCheck; @@ -643,7 +646,7 @@ namespace Validation std::string check(const std::string & schemaName, const JsonNode & data, ValidationData & validator) { validator.usedSchemas.push_back(schemaName); - auto onscopeExit = vstd::makeScopeGuard([&]() + auto onscopeExit = vstd::makeScopeGuard([&validator]() { validator.usedSchemas.pop_back(); }); @@ -659,8 +662,6 @@ namespace Validation auto checker = knownFields.find(entry.first); if (checker != knownFields.end()) errors += checker->second(validator, schema, entry.second, data); - //else - // errors += validator.makeErrorMessage("Unknown entry in schema " + entry.first); } return errors; } @@ -687,7 +688,7 @@ namespace Validation const TFormatMap & getKnownFormats() { - static TFormatMap knownFormats = createFormatMap(); + static const TFormatMap knownFormats = createFormatMap(); return knownFormats; } diff --git a/lib/json/JsonWriter.cpp b/lib/json/JsonWriter.cpp index a401c5da9..6bc65bae5 100644 --- a/lib/json/JsonWriter.cpp +++ b/lib/json/JsonWriter.cpp @@ -60,7 +60,7 @@ void JsonWriter::writeEntry(JsonVector::const_iterator entry) void JsonWriter::writeString(const std::string & string) { static const std::string escaped = "\"\\\b\f\n\r\t"; - static const std::array escaped_code = {'\"', '\\', 'b', 'f', 'n', 'r', 't'}; + static const std::array escapedCode = {'\"', '\\', 'b', 'f', 'n', 'r', 't'}; out << '\"'; size_t pos = 0; @@ -68,7 +68,7 @@ void JsonWriter::writeString(const std::string & string) for(; pos < string.size(); pos++) { //we need to check if special character was been already escaped - if((string[pos] == '\\') && (pos + 1 < string.size()) && (std::find(escaped_code.begin(), escaped_code.end(), string[pos + 1]) != escaped_code.end())) + if((string[pos] == '\\') && (pos + 1 < string.size()) && (std::find(escapedCode.begin(), escapedCode.end(), string[pos + 1]) != escapedCode.end())) { pos++; //write unchanged, next simbol also checked } @@ -79,7 +79,7 @@ void JsonWriter::writeString(const std::string & string) if(escapedPos != std::string::npos) { out.write(string.data() + start, pos - start); - out << '\\' << escaped_code[escapedPos]; + out << '\\' << escapedCode[escapedPos]; start = pos + 1; } } diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index b5170bef8..3b0e447f1 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -354,7 +354,7 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const JsonVector & data = node["allowedTerrains"].Vector(); for(auto type : allowedTerrains) - data.push_back(JsonNode(VLC->terrainTypeHandler->getById(type)->getJsonKey())); + data.emplace_back(VLC->terrainTypeHandler->getById(type)->getJsonKey()); } } diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 7356dc2a3..f05bdab6f 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -226,7 +226,10 @@ void CModHandler::loadOneMod(std::string modName, const std::string & parent, co if(CResourceHandler::get("initial")->existsResource(CModInfo::getModFile(modFullName))) { - CModInfo mod(modFullName, modSettings[modName], JsonNode(CModInfo::getModFile(modFullName))); + JsonParsingSettings settings; + settings.mode = JsonParsingSettings::JsonFormatMode::JSON; // TODO: remove once Android launcher with its strict parser is gone + + CModInfo mod(modFullName, modSettings[modName], JsonNode(CModInfo::getModFile(modFullName), settings)); if (!parent.empty()) // this is submod, add parent to dependencies mod.dependencies.insert(parent); diff --git a/test/scripting/LuaSpellEffectAPITest.cpp b/test/scripting/LuaSpellEffectAPITest.cpp index dd3364057..a144485a3 100644 --- a/test/scripting/LuaSpellEffectAPITest.cpp +++ b/test/scripting/LuaSpellEffectAPITest.cpp @@ -84,8 +84,8 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplicableOnLeftSideOfField) BattleHex hex(2,2); JsonNode first; - first.Vector().push_back(JsonNode(hex.hex)); - first.Vector().push_back(JsonNode()); + first.Vector().emplace_back(hex.hex); + first.Vector().emplace_back(); JsonNode targets; targets.Vector().push_back(first); diff --git a/test/scripting/LuaSpellEffectTest.cpp b/test/scripting/LuaSpellEffectTest.cpp index 01a95b1a3..a550f57cc 100644 --- a/test/scripting/LuaSpellEffectTest.cpp +++ b/test/scripting/LuaSpellEffectTest.cpp @@ -154,12 +154,12 @@ TEST_F(LuaSpellEffectTest, ApplicableTargetRedirected) JsonNode first; - first.Vector().push_back(JsonNode(hex1.hex)); - first.Vector().push_back(JsonNode(id1)); + first.Vector().emplace_back(hex1.hex); + first.Vector().emplace_back(id1); JsonNode second; - second.Vector().push_back(JsonNode(hex2.hex)); - second.Vector().push_back(JsonNode(-1)); + second.Vector().emplace_back(hex2.hex); + second.Vector().emplace_back(-1); JsonNode targets; targets.Vector().push_back(first); @@ -193,8 +193,8 @@ TEST_F(LuaSpellEffectTest, ApplyRedirected) subject->apply(&serverMock, &mechanicsMock, target); JsonNode first; - first.Vector().push_back(JsonNode(hex1.hex)); - first.Vector().push_back(JsonNode(id1)); + first.Vector().emplace_back(hex1.hex); + first.Vector().emplace_back(id1); JsonNode targets; targets.Vector().push_back(first); From 757f77378d70f2d327a8431c1f5d30f2957be5dd Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 14 Feb 2024 20:35:58 +0200 Subject: [PATCH 153/250] Remove unused code --- lib/json/JsonUtils.cpp | 111 ++--------------------------------------- lib/json/JsonUtils.h | 16 ------ 2 files changed, 4 insertions(+), 123 deletions(-) diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 69851c6ca..9ddea0dfb 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -19,21 +19,6 @@ VCMI_LIB_NAMESPACE_BEGIN static const JsonNode nullNode; -//returns first Key with value equal to given one -template -Key reverseMapFirst(const Val & val, const std::map & map) -{ - for(auto it : map) - { - if(it.second == val) - { - return it.first; - } - } - assert(0); - return ""; -} - static JsonNode getDefaultValue(const JsonNode & schema, std::string fieldName) { const JsonNode & fieldProps = schema["properties"][fieldName]; @@ -65,8 +50,8 @@ static void eraseOptionalNodes(JsonNode & node, const JsonNode & schema) for(const auto & entry : schema["required"].Vector()) foundEntries.insert(entry.String()); - vstd::erase_if(node.Struct(), [&](const auto & node){ - return !vstd::contains(foundEntries, node.first); + vstd::erase_if(node.Struct(), [&foundEntries](const auto & structEntry){ + return !vstd::contains(foundEntries, structEntry.first); }); } @@ -242,90 +227,6 @@ void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base) std::swap(descendant, inheritedNode); } -JsonNode JsonUtils::intersect(const std::vector & nodes, bool pruneEmpty) -{ - if(nodes.empty()) - return nullNode; - - JsonNode result = nodes[0]; - for(int i = 1; i < nodes.size(); i++) - { - if(result.isNull()) - break; - result = JsonUtils::intersect(result, nodes[i], pruneEmpty); - } - return result; -} - -JsonNode JsonUtils::intersect(const JsonNode & a, const JsonNode & b, bool pruneEmpty) -{ - if(a.getType() == JsonNode::JsonType::DATA_STRUCT && b.getType() == JsonNode::JsonType::DATA_STRUCT) - { - // intersect individual properties - JsonNode result; - for(const auto & property : a.Struct()) - { - if(vstd::contains(b.Struct(), property.first)) - { - JsonNode propertyIntersect = JsonUtils::intersect(property.second, b.Struct().find(property.first)->second); - if(pruneEmpty && !propertyIntersect.containsBaseData()) - continue; - result[property.first] = propertyIntersect; - } - } - return result; - } - else - { - // not a struct - same or different, no middle ground - if(a == b) - return a; - } - return nullNode; -} - -JsonNode JsonUtils::difference(const JsonNode & node, const JsonNode & base) -{ - auto addsInfo = [](JsonNode diff) -> bool - { - switch(diff.getType()) - { - case JsonNode::JsonType::DATA_NULL: - return false; - case JsonNode::JsonType::DATA_STRUCT: - return !diff.Struct().empty(); - default: - return true; - } - }; - - if(node.getType() == JsonNode::JsonType::DATA_STRUCT && base.getType() == JsonNode::JsonType::DATA_STRUCT) - { - // subtract individual properties - JsonNode result; - for(const auto & property : node.Struct()) - { - if(vstd::contains(base.Struct(), property.first)) - { - const JsonNode propertyDifference = JsonUtils::difference(property.second, base.Struct().find(property.first)->second); - if(addsInfo(propertyDifference)) - result[property.first] = propertyDifference; - } - else - { - result[property.first] = property.second; - } - } - return result; - } - else - { - if(node == base) - return nullNode; - } - return node; -} - JsonNode JsonUtils::assembleFromFiles(const std::vector & files) { bool isValid = false; @@ -354,12 +255,8 @@ JsonNode JsonUtils::assembleFromFiles(const std::string & filename) for(auto & loader : CResourceHandler::get()->getResourcesWithName(resID)) { - // FIXME: some way to make this code more readable - auto stream = loader->load(resID); - std::unique_ptr textData(new ui8[stream->getSize()]); - stream->read(textData.get(), stream->getSize()); - - JsonNode section(reinterpret_cast(textData.get()), stream->getSize()); + auto textData = loader->load(resID)->readAll(); + JsonNode section(reinterpret_cast(textData.first.get()), textData.second); merge(result, section); } return result; diff --git a/lib/json/JsonUtils.h b/lib/json/JsonUtils.h index 33d01f888..7f550b916 100644 --- a/lib/json/JsonUtils.h +++ b/lib/json/JsonUtils.h @@ -40,22 +40,6 @@ namespace JsonUtils */ DLL_LINKAGE void inherit(JsonNode & descendant, const JsonNode & base); - /** - * @brief construct node representing the common structure of input nodes - * @param pruneEmpty - omit common properties whose intersection is empty - * different types: null - * struct: recursive intersect on common properties - * other: input if equal, null otherwise - */ - DLL_LINKAGE JsonNode intersect(const JsonNode & a, const JsonNode & b, bool pruneEmpty = true); - DLL_LINKAGE JsonNode intersect(const std::vector & nodes, bool pruneEmpty = true); - - /** - * @brief construct node representing the difference "node - base" - * merging difference with base gives node - */ - DLL_LINKAGE JsonNode difference(const JsonNode & node, const JsonNode & base); - /** * @brief generate one Json structure from multiple files * @param files - list of filenames with parts of json structure From 08deae41865ccfaf05459175caf331b7ff1933af Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 14 Feb 2024 20:45:14 +0200 Subject: [PATCH 154/250] Moved static methods outside of vcmi namespace --- lib/json/JsonBonus.cpp | 202 +++++++++++++++++++++-------------------- lib/json/JsonBonus.h | 14 +-- lib/json/JsonUtils.cpp | 4 +- 3 files changed, 112 insertions(+), 108 deletions(-) diff --git a/lib/json/JsonBonus.cpp b/lib/json/JsonBonus.cpp index 76b5f77ab..b56de5786 100644 --- a/lib/json/JsonBonus.cpp +++ b/lib/json/JsonBonus.cpp @@ -22,7 +22,37 @@ #include "../constants/StringConstants.h" #include "../modding/IdentifierStorage.h" -VCMI_LIB_NAMESPACE_BEGIN +VCMI_LIB_USING_NAMESPACE + +template +const T parseByMap(const std::map & map, const JsonNode * val, const std::string & err) +{ + if (!val->isNull()) + { + const std::string & type = val->String(); + auto it = map.find(type); + if (it == map.end()) + { + logMod->error("Error: invalid %s%s.", err, type); + return {}; + } + else + { + return it->second; + } + } + else + return {}; +} + +template +const T parseByMapN(const std::map & map, const JsonNode * val, const std::string & err) +{ + if(val->isNumber()) + return static_cast(val->Integer()); + else + return parseByMap(map, val, err); +} static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node) { @@ -264,6 +294,77 @@ static void loadBonusSourceInstance(BonusSourceID & sourceInstance, BonusSource } } +static BonusParams convertDeprecatedBonus(const JsonNode &ability) +{ + if(vstd::contains(deprecatedBonusSet, ability["type"].String())) + { + logMod->warn("There is deprecated bonus found:\n%s\nTrying to convert...", ability.toString()); + auto params = BonusParams(ability["type"].String(), + ability["subtype"].isString() ? ability["subtype"].String() : "", + ability["subtype"].isNumber() ? ability["subtype"].Integer() : -1); + if(params.isConverted) + { + if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && bonusValueMap.find(ability["valueType"].String())->second == BonusValueType::PERCENT_TO_BASE) //assume secondary skill special + { + params.valueType = BonusValueType::PERCENT_TO_TARGET_TYPE; + params.targetType = BonusSource::SECONDARY_SKILL; + } + + logMod->warn("Please, use this bonus:\n%s\nConverted successfully!", params.toJson().toString()); + return params; + } + else + logMod->error("Cannot convert bonus!\n%s", ability.toString()); + } + BonusParams ret; + ret.isConverted = false; + return ret; +} + +static TUpdaterPtr parseUpdater(const JsonNode & updaterJson) +{ + switch(updaterJson.getType()) + { + case JsonNode::JsonType::DATA_STRING: + return parseByMap(bonusUpdaterMap, &updaterJson, "updater type "); + break; + case JsonNode::JsonType::DATA_STRUCT: + if(updaterJson["type"].String() == "GROWS_WITH_LEVEL") + { + auto updater = std::make_shared(); + const JsonVector param = updaterJson["parameters"].Vector(); + updater->valPer20 = static_cast(param[0].Integer()); + if(param.size() > 1) + updater->stepSize = static_cast(param[1].Integer()); + return updater; + } + else if (updaterJson["type"].String() == "ARMY_MOVEMENT") + { + auto updater = std::make_shared(); + if(updaterJson["parameters"].isVector()) + { + const auto & param = updaterJson["parameters"].Vector(); + if(param.size() < 4) + logMod->warn("Invalid ARMY_MOVEMENT parameters, using default!"); + else + { + updater->base = static_cast(param.at(0).Integer()); + updater->divider = static_cast(param.at(1).Integer()); + updater->multiplier = static_cast(param.at(2).Integer()); + updater->max = static_cast(param.at(3).Integer()); + } + return updater; + } + } + else + logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String()); + break; + } + return nullptr; +} + +VCMI_LIB_NAMESPACE_BEGIN + std::shared_ptr JsonUtils::parseBonus(const JsonVector & ability_vec) { auto b = std::make_shared(); @@ -284,36 +385,6 @@ std::shared_ptr JsonUtils::parseBonus(const JsonVector & ability_vec) return b; } -template -const T parseByMap(const std::map & map, const JsonNode * val, const std::string & err) -{ - if (!val->isNull()) - { - const std::string & type = val->String(); - auto it = map.find(type); - if (it == map.end()) - { - logMod->error("Error: invalid %s%s.", err, type); - return {}; - } - else - { - return it->second; - } - } - else - return {}; -} - -template -const T parseByMapN(const std::map & map, const JsonNode * val, const std::string & err) -{ - if(val->isNumber()) - return static_cast(val->Integer()); - else - return parseByMap(map, val, err); -} - void JsonUtils::resolveAddInfo(CAddInfo & var, const JsonNode & node) { const JsonNode & value = node["addInfo"]; @@ -567,75 +638,6 @@ std::shared_ptr JsonUtils::parseBuildingBonus(const JsonNode & ability, c return b; } -static BonusParams convertDeprecatedBonus(const JsonNode &ability) -{ - if(vstd::contains(deprecatedBonusSet, ability["type"].String())) - { - logMod->warn("There is deprecated bonus found:\n%s\nTrying to convert...", ability.toString()); - auto params = BonusParams(ability["type"].String(), - ability["subtype"].isString() ? ability["subtype"].String() : "", - ability["subtype"].isNumber() ? ability["subtype"].Integer() : -1); - if(params.isConverted) - { - if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && bonusValueMap.find(ability["valueType"].String())->second == BonusValueType::PERCENT_TO_BASE) //assume secondary skill special - { - params.valueType = BonusValueType::PERCENT_TO_TARGET_TYPE; - params.targetType = BonusSource::SECONDARY_SKILL; - } - - logMod->warn("Please, use this bonus:\n%s\nConverted successfully!", params.toJson().toString()); - return params; - } - else - logMod->error("Cannot convert bonus!\n%s", ability.toString()); - } - BonusParams ret; - ret.isConverted = false; - return ret; -} - -static TUpdaterPtr parseUpdater(const JsonNode & updaterJson) -{ - switch(updaterJson.getType()) - { - case JsonNode::JsonType::DATA_STRING: - return parseByMap(bonusUpdaterMap, &updaterJson, "updater type "); - break; - case JsonNode::JsonType::DATA_STRUCT: - if(updaterJson["type"].String() == "GROWS_WITH_LEVEL") - { - auto updater = std::make_shared(); - const JsonVector param = updaterJson["parameters"].Vector(); - updater->valPer20 = static_cast(param[0].Integer()); - if(param.size() > 1) - updater->stepSize = static_cast(param[1].Integer()); - return updater; - } - else if (updaterJson["type"].String() == "ARMY_MOVEMENT") - { - auto updater = std::make_shared(); - if(updaterJson["parameters"].isVector()) - { - const auto & param = updaterJson["parameters"].Vector(); - if(param.size() < 4) - logMod->warn("Invalid ARMY_MOVEMENT parameters, using default!"); - else - { - updater->base = static_cast(param.at(0).Integer()); - updater->divider = static_cast(param.at(1).Integer()); - updater->multiplier = static_cast(param.at(2).Integer()); - updater->max = static_cast(param.at(3).Integer()); - } - return updater; - } - } - else - logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String()); - break; - } - return nullptr; -} - bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b) { const JsonNode * value = nullptr; diff --git a/lib/json/JsonBonus.h b/lib/json/JsonBonus.h index dd2b8fa12..db5ed492c 100644 --- a/lib/json/JsonBonus.h +++ b/lib/json/JsonBonus.h @@ -21,13 +21,13 @@ class CAddInfo; namespace JsonUtils { - DLL_LINKAGE std::shared_ptr parseBonus(const JsonVector & ability_vec); - DLL_LINKAGE std::shared_ptr parseBonus(const JsonNode & ability); - DLL_LINKAGE std::shared_ptr parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description); - DLL_LINKAGE bool parseBonus(const JsonNode & ability, Bonus * placement); - DLL_LINKAGE std::shared_ptr parseLimiter(const JsonNode & limiter); - DLL_LINKAGE CSelector parseSelector(const JsonNode &ability); - DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node); + std::shared_ptr parseBonus(const JsonVector & ability_vec); + std::shared_ptr parseBonus(const JsonNode & ability); + std::shared_ptr parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description); + bool parseBonus(const JsonNode & ability, Bonus * placement); + std::shared_ptr parseLimiter(const JsonNode & limiter); + CSelector parseSelector(const JsonNode &ability); + void resolveAddInfo(CAddInfo & var, const JsonNode & node); } VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 9ddea0dfb..62d2fdebd 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -15,7 +15,7 @@ #include "../filesystem/Filesystem.h" -VCMI_LIB_NAMESPACE_BEGIN +VCMI_LIB_USING_NAMESPACE static const JsonNode nullNode; @@ -91,6 +91,8 @@ static void maximizeNode(JsonNode & node, const JsonNode & schema) eraseOptionalNodes(node, schema); } +VCMI_LIB_NAMESPACE_BEGIN + void JsonUtils::minimize(JsonNode & node, const std::string & schemaName) { minimizeNode(node, getSchema(schemaName)); From d2844a5eebde1ea60692a9bb53060c609f5b376e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 15 Feb 2024 22:29:56 +0200 Subject: [PATCH 155/250] Cleared up code --- lib/json/JsonNode.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index 7ace2fc3f..443bc068a 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -401,7 +401,7 @@ si64 JsonNode::Integer() const const std::string & JsonNode::String() const { - static const std::string stringDefault = std::string(); + static const std::string stringDefault; assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRING); @@ -413,7 +413,7 @@ const std::string & JsonNode::String() const const JsonVector & JsonNode::Vector() const { - static const JsonVector vectorDefault = JsonVector(); + static const JsonVector vectorDefault; assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_VECTOR); @@ -425,7 +425,7 @@ const JsonVector & JsonNode::Vector() const const JsonMap & JsonNode::Struct() const { - static const JsonMap mapDefault = JsonMap(); + static const JsonMap mapDefault; assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRUCT); From c90fb47c23d75f720f99b41d5c82b9d0e2edf568 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Feb 2024 17:46:26 +0200 Subject: [PATCH 156/250] Converted json validator into a class --- lib/json/JsonFormatException.h | 2 +- lib/json/JsonNode.h | 2 +- lib/json/JsonUtils.cpp | 3 +- lib/json/JsonValidator.cpp | 1203 +++++++++++++++----------------- lib/json/JsonValidator.h | 32 +- 5 files changed, 599 insertions(+), 643 deletions(-) diff --git a/lib/json/JsonFormatException.h b/lib/json/JsonFormatException.h index 2d385029d..7e9f0fdc0 100644 --- a/lib/json/JsonFormatException.h +++ b/lib/json/JsonFormatException.h @@ -11,7 +11,7 @@ VCMI_LIB_NAMESPACE_BEGIN -class JsonFormatException : public std::runtime_error +class DLL_LINKAGE JsonFormatException : public std::runtime_error { public: using runtime_error::runtime_error; diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index 0aff27e89..83a606a3a 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -17,7 +17,7 @@ class JsonNode; using JsonMap = std::map; using JsonVector = std::vector; -struct JsonParsingSettings +struct DLL_LINKAGE JsonParsingSettings { enum class JsonFormatMode { diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 62d2fdebd..c66823df5 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -105,7 +105,8 @@ void JsonUtils::maximize(JsonNode & node, const std::string & schemaName) bool JsonUtils::validate(const JsonNode & node, const std::string & schemaName, const std::string & dataName) { - std::string log = Validation::check(schemaName, node); + JsonValidator validator; + std::string log = validator.check(schemaName, node); if (!log.empty()) { logMod->warn("Data in %s is invalid!", dataName); diff --git a/lib/json/JsonValidator.cpp b/lib/json/JsonValidator.cpp index 6c1cff192..14dca501d 100644 --- a/lib/json/JsonValidator.cpp +++ b/lib/json/JsonValidator.cpp @@ -21,677 +21,638 @@ VCMI_LIB_NAMESPACE_BEGIN -static const std::unordered_map stringToType = +static std::string emptyCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) { - {"null", JsonNode::JsonType::DATA_NULL}, - {"boolean", JsonNode::JsonType::DATA_BOOL}, - {"number", JsonNode::JsonType::DATA_FLOAT}, - {"integer", JsonNode::JsonType::DATA_INTEGER}, - {"string", JsonNode::JsonType::DATA_STRING}, - {"array", JsonNode::JsonType::DATA_VECTOR}, - {"object", JsonNode::JsonType::DATA_STRUCT} -}; + // check is not needed - e.g. incorporated into another check + return ""; +} -namespace +static std::string notImplementedCheck(JsonValidator & validator, + const JsonNode & baseSchema, + const JsonNode & schema, + const JsonNode & data) { - namespace Common + return "Not implemented entry in schema"; +} + +static std::string schemaListCheck(JsonValidator & validator, + const JsonNode & baseSchema, + const JsonNode & schema, + const JsonNode & data, + const std::string & errorMsg, + const std::function & isValid) +{ + std::string errors = "\n"; + size_t result = 0; + + for(const auto & schemaEntry : schema.Vector()) { - std::string emptyCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + std::string error = validator.check(schemaEntry, data); + if (error.empty()) { - // check is not needed - e.g. incorporated into another check + result++; + } + else + { + errors += error; + errors += "\n"; + } + } + if (isValid(result)) + return ""; + else + return validator.makeErrorMessage(errorMsg) + errors; +} + +static std::string allOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass all schemas", [&schema](size_t count) + { + return count == schema.Vector().size(); + }); +} + +static std::string anyOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass any schema", [](size_t count) + { + return count > 0; + }); +} + +static std::string oneOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass exactly one schema", [](size_t count) + { + return count == 1; + }); +} + +static std::string notCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + if (validator.check(schema, data).empty()) + return validator.makeErrorMessage("Successful validation against negative check"); + return ""; +} + +static std::string enumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + for(const auto & enumEntry : schema.Vector()) + { + if (data == enumEntry) return ""; - } + } + return validator.makeErrorMessage("Key must have one of predefined values"); +} - std::string notImplementedCheck(Validation::ValidationData & validator, - const JsonNode & baseSchema, - const JsonNode & schema, - const JsonNode & data) +static std::string typeCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + static const std::unordered_map stringToType = + { + {"null", JsonNode::JsonType::DATA_NULL}, + {"boolean", JsonNode::JsonType::DATA_BOOL}, + {"number", JsonNode::JsonType::DATA_FLOAT}, + {"integer", JsonNode::JsonType::DATA_INTEGER}, + {"string", JsonNode::JsonType::DATA_STRING}, + {"array", JsonNode::JsonType::DATA_VECTOR}, + {"object", JsonNode::JsonType::DATA_STRUCT} + }; + + const auto & typeName = schema.String(); + auto it = stringToType.find(typeName); + if(it == stringToType.end()) + { + return validator.makeErrorMessage("Unknown type in schema:" + typeName); + } + + JsonNode::JsonType type = it->second; + + // for "number" type both float and integer are allowed + if(type == JsonNode::JsonType::DATA_FLOAT && data.isNumber()) + return ""; + + if(type != data.getType() && data.getType() != JsonNode::JsonType::DATA_NULL) + return validator.makeErrorMessage("Type mismatch! Expected " + schema.String()); + return ""; +} + +static std::string refCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + std::string URI = schema.String(); + //node must be validated using schema pointed by this reference and not by data here + //Local reference. Turn it into more easy to handle remote ref + if (boost::algorithm::starts_with(URI, "#")) + { + const std::string name = validator.usedSchemas.back(); + const std::string nameClean = name.substr(0, name.find('#')); + URI = nameClean + URI; + } + return validator.check(URI, data); +} + +static std::string formatCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + auto formats = validator.getKnownFormats(); + std::string errors; + auto checker = formats.find(schema.String()); + if (checker != formats.end()) + { + if (data.isString()) { - return "Not implemented entry in schema"; + std::string result = checker->second(data); + if (!result.empty()) + errors += validator.makeErrorMessage(result); } - - std::string schemaListCheck(Validation::ValidationData & validator, - const JsonNode & baseSchema, - const JsonNode & schema, - const JsonNode & data, - const std::string & errorMsg, - const std::function & isValid) + else { - std::string errors = "\n"; - size_t result = 0; + errors += validator.makeErrorMessage("Format value must be string: " + schema.String()); + } + } + else + errors += validator.makeErrorMessage("Unsupported format type: " + schema.String()); + return errors; +} - for(const auto & schemaEntry : schema.Vector()) +static std::string maxLengthCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + if (data.String().size() > schema.Float()) + return validator.makeErrorMessage((boost::format("String is longer than %d symbols") % schema.Float()).str()); + return ""; +} + +static std::string minLengthCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + if (data.String().size() < schema.Float()) + return validator.makeErrorMessage((boost::format("String is shorter than %d symbols") % schema.Float()).str()); + return ""; +} + +static std::string maximumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + if (data.Float() > schema.Float()) + return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str()); + return ""; +} + +static std::string minimumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + if (data.Float() < schema.Float()) + return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str()); + return ""; +} + +static std::string exclusiveMaximumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + if (data.Float() >= schema.Float()) + return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str()); + return ""; +} + +static std::string exclusiveMinimumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + if (data.Float() <= schema.Float()) + return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str()); + return ""; +} + +static std::string multipleOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + double result = data.Integer() / schema.Integer(); + if (!vstd::isAlmostEqual(floor(result), result)) + return validator.makeErrorMessage((boost::format("Value is not divisible by %d") % schema.Float()).str()); + return ""; +} + +static std::string itemEntryCheck(JsonValidator & validator, const JsonVector & items, const JsonNode & schema, size_t index) +{ + validator.currentPath.emplace_back(); + validator.currentPath.back().Float() = static_cast(index); + auto onExit = vstd::makeScopeGuard([&validator]() + { + validator.currentPath.pop_back(); + }); + + if (!schema.isNull()) + return validator.check(schema, items[index]); + return ""; +} + +static std::string itemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + std::string errors; + for (size_t i=0; i i) + errors += itemEntryCheck(validator, data.Vector(), schema.Vector()[i], i); + } + else + { + errors += itemEntryCheck(validator, data.Vector(), schema, i); + } + } + return errors; +} + +static std::string additionalItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + std::string errors; + // "items" is struct or empty (defaults to empty struct) - validation always successful + const JsonNode & items = baseSchema["items"]; + if (items.getType() != JsonNode::JsonType::DATA_VECTOR) + return ""; + + for (size_t i=items.Vector().size(); i schema.Float()) + return validator.makeErrorMessage((boost::format("Length is bigger than %d") % schema.Float()).str()); + return ""; +} + +static std::string uniqueItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + if (schema.Bool()) + { + for (auto itA = schema.Vector().begin(); itA != schema.Vector().end(); itA++) + { + auto itB = itA; + while (++itB != schema.Vector().end()) { - std::string error = check(schemaEntry, data, validator); - if (error.empty()) - { - result++; - } - else - { - errors += error; - errors += "\n"; - } + if (*itA == *itB) + return validator.makeErrorMessage("List must consist from unique items"); } - if (isValid(result)) - return ""; - else - return validator.makeErrorMessage(errorMsg) + errors; } + } + return ""; +} - std::string allOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +static std::string maxPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + if (data.Struct().size() > schema.Float()) + return validator.makeErrorMessage((boost::format("Number of entries is bigger than %d") % schema.Float()).str()); + return ""; +} + +static std::string minPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + if (data.Struct().size() < schema.Float()) + return validator.makeErrorMessage((boost::format("Number of entries is less than %d") % schema.Float()).str()); + return ""; +} + +static std::string uniquePropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + for (auto itA = data.Struct().begin(); itA != data.Struct().end(); itA++) + { + auto itB = itA; + while (++itB != data.Struct().end()) { - return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass all schemas", [&schema](size_t count) + if (itA->second == itB->second) + return validator.makeErrorMessage("List must consist from unique items"); + } + } + return ""; +} + +static std::string requiredCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + std::string errors; + for(const auto & required : schema.Vector()) + { + if (data[required.String()].isNull()) + errors += validator.makeErrorMessage("Required entry " + required.String() + " is missing"); + } + return errors; +} + +static std::string dependenciesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + std::string errors; + for(const auto & deps : schema.Struct()) + { + if (!data[deps.first].isNull()) + { + if (deps.second.getType() == JsonNode::JsonType::DATA_VECTOR) { - return count == schema.Vector().size(); - }); - } - - std::string anyOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass any schema", [](size_t count) - { - return count > 0; - }); - } - - std::string oneOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass exactly one schema", [](size_t count) - { - return count == 1; - }); - } - - std::string notCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (check(schema, data, validator).empty()) - return validator.makeErrorMessage("Successful validation against negative check"); - return ""; - } - - std::string enumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - for(const auto & enumEntry : schema.Vector()) - { - if (data == enumEntry) - return ""; - } - return validator.makeErrorMessage("Key must have one of predefined values"); - } - - std::string typeCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - const auto & typeName = schema.String(); - auto it = stringToType.find(typeName); - if(it == stringToType.end()) - { - return validator.makeErrorMessage("Unknown type in schema:" + typeName); - } - - JsonNode::JsonType type = it->second; - - // for "number" type both float and integer are allowed - if(type == JsonNode::JsonType::DATA_FLOAT && data.isNumber()) - return ""; - - if(type != data.getType() && data.getType() != JsonNode::JsonType::DATA_NULL) - return validator.makeErrorMessage("Type mismatch! Expected " + schema.String()); - return ""; - } - - std::string refCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string URI = schema.String(); - //node must be validated using schema pointed by this reference and not by data here - //Local reference. Turn it into more easy to handle remote ref - if (boost::algorithm::starts_with(URI, "#")) - { - const std::string name = validator.usedSchemas.back(); - const std::string nameClean = name.substr(0, name.find('#')); - URI = nameClean + URI; - } - return check(URI, data, validator); - } - - std::string formatCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - auto formats = Validation::getKnownFormats(); - std::string errors; - auto checker = formats.find(schema.String()); - if (checker != formats.end()) - { - if (data.isString()) + JsonVector depList = deps.second.Vector(); + for(auto & depEntry : depList) { - std::string result = checker->second(data); - if (!result.empty()) - errors += validator.makeErrorMessage(result); - } - else - { - errors += validator.makeErrorMessage("Format value must be string: " + schema.String()); + if (data[depEntry.String()].isNull()) + errors += validator.makeErrorMessage("Property " + depEntry.String() + " required for " + deps.first + " is missing"); } } else - errors += validator.makeErrorMessage("Unsupported format type: " + schema.String()); - return errors; + { + if (!validator.check(deps.second, data).empty()) + errors += validator.makeErrorMessage("Requirements for " + deps.first + " are not fulfilled"); + } } } + return errors; +} - namespace String +static std::string propertyEntryCheck(JsonValidator & validator, const JsonNode &node, const JsonNode & schema, const std::string & nodeName) +{ + validator.currentPath.emplace_back(); + validator.currentPath.back().String() = nodeName; + auto onExit = vstd::makeScopeGuard([&validator]() { - std::string maxLengthCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (data.String().size() > schema.Float()) - return validator.makeErrorMessage((boost::format("String is longer than %d symbols") % schema.Float()).str()); - return ""; - } + validator.currentPath.pop_back(); + }); - std::string minLengthCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) + // there is schema specifically for this item + if (!schema.isNull()) + return validator.check(schema, node); + return ""; +} + +static std::string propertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + std::string errors; + + for(const auto & entry : data.Struct()) + errors += propertyEntryCheck(validator, entry.second, schema[entry.first], entry.first); + return errors; +} + +static std::string additionalPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + std::string errors; + for(const auto & entry : data.Struct()) + { + if (baseSchema["properties"].Struct().count(entry.first) == 0) { - if (data.String().size() < schema.Float()) - return validator.makeErrorMessage((boost::format("String is shorter than %d symbols") % schema.Float()).str()); - return ""; + // try generic additionalItems schema + if (schema.getType() == JsonNode::JsonType::DATA_STRUCT) + errors += propertyEntryCheck(validator, entry.second, schema, entry.first); + + // or, additionalItems field can be bool which indicates if such items are allowed + else if(!schema.isNull() && !schema.Bool()) // present and set to false - error + errors += validator.makeErrorMessage("Unknown entry found: " + entry.first); } } + return errors; +} - namespace Number +static bool testFilePresence(const std::string & scope, const ResourcePath & resource) +{ + std::set allowedScopes; + if(scope != ModScope::scopeBuiltin() && !scope.empty()) // all real mods may have dependencies { + //NOTE: recursive dependencies are not allowed at the moment - update code if this changes + bool found = true; + allowedScopes = VLC->modh->getModDependencies(scope, found); - std::string maximumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (data.Float() > schema.Float()) - return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str()); - return ""; - } - - std::string minimumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (data.Float() < schema.Float()) - return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str()); - return ""; - } - - std::string exclusiveMaximumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (data.Float() >= schema.Float()) - return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str()); - return ""; - } - - std::string exclusiveMinimumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (data.Float() <= schema.Float()) - return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str()); - return ""; - } - - std::string multipleOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - double result = data.Integer() / schema.Integer(); - if (!vstd::isAlmostEqual(floor(result), result)) - return validator.makeErrorMessage((boost::format("Value is not divisible by %d") % schema.Float()).str()); - return ""; - } - } - - namespace Vector - { - std::string itemEntryCheck(Validation::ValidationData & validator, const JsonVector & items, const JsonNode & schema, size_t index) - { - validator.currentPath.emplace_back(); - validator.currentPath.back().Float() = static_cast(index); - auto onExit = vstd::makeScopeGuard([&validator]() - { - validator.currentPath.pop_back(); - }); - - if (!schema.isNull()) - return check(schema, items[index], validator); - return ""; - } - - std::string itemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string errors; - for (size_t i=0; i i) - errors += itemEntryCheck(validator, data.Vector(), schema.Vector()[i], i); - } - else - { - errors += itemEntryCheck(validator, data.Vector(), schema, i); - } - } - return errors; - } - - std::string additionalItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string errors; - // "items" is struct or empty (defaults to empty struct) - validation always successful - const JsonNode & items = baseSchema["items"]; - if (items.getType() != JsonNode::JsonType::DATA_VECTOR) - return ""; - - for (size_t i=items.Vector().size(); i schema.Float()) - return validator.makeErrorMessage((boost::format("Length is bigger than %d") % schema.Float()).str()); - return ""; - } - - std::string uniqueItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (schema.Bool()) - { - for (auto itA = schema.Vector().begin(); itA != schema.Vector().end(); itA++) - { - auto itB = itA; - while (++itB != schema.Vector().end()) - { - if (*itA == *itB) - return validator.makeErrorMessage("List must consist from unique items"); - } - } - } - return ""; - } - } - - namespace Struct - { - std::string maxPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (data.Struct().size() > schema.Float()) - return validator.makeErrorMessage((boost::format("Number of entries is bigger than %d") % schema.Float()).str()); - return ""; - } - - std::string minPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - if (data.Struct().size() < schema.Float()) - return validator.makeErrorMessage((boost::format("Number of entries is less than %d") % schema.Float()).str()); - return ""; - } - - std::string uniquePropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - for (auto itA = data.Struct().begin(); itA != data.Struct().end(); itA++) - { - auto itB = itA; - while (++itB != data.Struct().end()) - { - if (itA->second == itB->second) - return validator.makeErrorMessage("List must consist from unique items"); - } - } - return ""; - } - - std::string requiredCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string errors; - for(const auto & required : schema.Vector()) - { - if (data[required.String()].isNull()) - errors += validator.makeErrorMessage("Required entry " + required.String() + " is missing"); - } - return errors; - } - - std::string dependenciesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string errors; - for(const auto & deps : schema.Struct()) - { - if (!data[deps.first].isNull()) - { - if (deps.second.getType() == JsonNode::JsonType::DATA_VECTOR) - { - JsonVector depList = deps.second.Vector(); - for(auto & depEntry : depList) - { - if (data[depEntry.String()].isNull()) - errors += validator.makeErrorMessage("Property " + depEntry.String() + " required for " + deps.first + " is missing"); - } - } - else - { - if (!check(deps.second, data, validator).empty()) - errors += validator.makeErrorMessage("Requirements for " + deps.first + " are not fulfilled"); - } - } - } - return errors; - } - - std::string propertyEntryCheck(Validation::ValidationData & validator, const JsonNode &node, const JsonNode & schema, const std::string & nodeName) - { - validator.currentPath.emplace_back(); - validator.currentPath.back().String() = nodeName; - auto onExit = vstd::makeScopeGuard([&validator]() - { - validator.currentPath.pop_back(); - }); - - // there is schema specifically for this item - if (!schema.isNull()) - return check(schema, node, validator); - return ""; - } - - std::string propertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string errors; - - for(const auto & entry : data.Struct()) - errors += propertyEntryCheck(validator, entry.second, schema[entry.first], entry.first); - return errors; - } - - std::string additionalPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) - { - std::string errors; - for(const auto & entry : data.Struct()) - { - if (baseSchema["properties"].Struct().count(entry.first) == 0) - { - // try generic additionalItems schema - if (schema.getType() == JsonNode::JsonType::DATA_STRUCT) - errors += propertyEntryCheck(validator, entry.second, schema, entry.first); - - // or, additionalItems field can be bool which indicates if such items are allowed - else if(!schema.isNull() && !schema.Bool()) // present and set to false - error - errors += validator.makeErrorMessage("Unknown entry found: " + entry.first); - } - } - return errors; - } - } - - namespace Formats - { - bool testFilePresence(const std::string & scope, const ResourcePath & resource) - { - std::set allowedScopes; - if(scope != ModScope::scopeBuiltin() && !scope.empty()) // all real mods may have dependencies - { - //NOTE: recursive dependencies are not allowed at the moment - update code if this changes - bool found = true; - allowedScopes = VLC->modh->getModDependencies(scope, found); - - if(!found) - return false; - - allowedScopes.insert(ModScope::scopeBuiltin()); // all mods can use H3 files - } - allowedScopes.insert(scope); // mods can use their own files - - for(const auto & entry : allowedScopes) - { - if (CResourceHandler::get(entry)->existsResource(resource)) - return true; - } + if(!found) return false; - } - #define TEST_FILE(scope, prefix, file, type) \ - if (testFilePresence(scope, ResourcePath(prefix + file, type))) \ - return "" - - std::string testAnimation(const std::string & path, const std::string & scope) - { - TEST_FILE(scope, "Sprites/", path, EResType::ANIMATION); - TEST_FILE(scope, "Sprites/", path, EResType::JSON); - return "Animation file \"" + path + "\" was not found"; - } - - std::string textFile(const JsonNode & node) - { - TEST_FILE(node.getModScope(), "", node.String(), EResType::JSON); - return "Text file \"" + node.String() + "\" was not found"; - } - - std::string musicFile(const JsonNode & node) - { - TEST_FILE(node.getModScope(), "Music/", node.String(), EResType::SOUND); - TEST_FILE(node.getModScope(), "", node.String(), EResType::SOUND); - return "Music file \"" + node.String() + "\" was not found"; - } - - std::string soundFile(const JsonNode & node) - { - TEST_FILE(node.getModScope(), "Sounds/", node.String(), EResType::SOUND); - return "Sound file \"" + node.String() + "\" was not found"; - } - - std::string defFile(const JsonNode & node) - { - return testAnimation(node.String(), node.getModScope()); - } - - std::string animationFile(const JsonNode & node) - { - return testAnimation(node.String(), node.getModScope()); - } - - std::string imageFile(const JsonNode & node) - { - TEST_FILE(node.getModScope(), "Data/", node.String(), EResType::IMAGE); - TEST_FILE(node.getModScope(), "Sprites/", node.String(), EResType::IMAGE); - if (node.String().find(':') != std::string::npos) - return testAnimation(node.String().substr(0, node.String().find(':')), node.getModScope()); - return "Image file \"" + node.String() + "\" was not found"; - } - - std::string videoFile(const JsonNode & node) - { - TEST_FILE(node.getModScope(), "Video/", node.String(), EResType::VIDEO); - return "Video file \"" + node.String() + "\" was not found"; - } - - #undef TEST_FILE + allowedScopes.insert(ModScope::scopeBuiltin()); // all mods can use H3 files } + allowedScopes.insert(scope); // mods can use their own files - Validation::TValidatorMap createCommonFields() + for(const auto & entry : allowedScopes) { - Validation::TValidatorMap ret; - - ret["format"] = Common::formatCheck; - ret["allOf"] = Common::allOfCheck; - ret["anyOf"] = Common::anyOfCheck; - ret["oneOf"] = Common::oneOfCheck; - ret["enum"] = Common::enumCheck; - ret["type"] = Common::typeCheck; - ret["not"] = Common::notCheck; - ret["$ref"] = Common::refCheck; - - // fields that don't need implementation - ret["title"] = Common::emptyCheck; - ret["$schema"] = Common::emptyCheck; - ret["default"] = Common::emptyCheck; - ret["defaultIOS"] = Common::emptyCheck; - ret["defaultAndroid"] = Common::emptyCheck; - ret["defaultWindows"] = Common::emptyCheck; - ret["description"] = Common::emptyCheck; - ret["definitions"] = Common::emptyCheck; - - // Not implemented - ret["propertyNames"] = Common::emptyCheck; - ret["contains"] = Common::emptyCheck; - ret["const"] = Common::emptyCheck; - ret["examples"] = Common::emptyCheck; - - return ret; + if (CResourceHandler::get(entry)->existsResource(resource)) + return true; } + return false; +} - Validation::TValidatorMap createStringFields() +#define TEST_FILE(scope, prefix, file, type) \ + if (testFilePresence(scope, ResourcePath(prefix + file, type))) \ + return "" + +static std::string testAnimation(const std::string & path, const std::string & scope) +{ + TEST_FILE(scope, "Sprites/", path, EResType::ANIMATION); + TEST_FILE(scope, "Sprites/", path, EResType::JSON); + return "Animation file \"" + path + "\" was not found"; +} + +static std::string textFile(const JsonNode & node) +{ + TEST_FILE(node.getModScope(), "", node.String(), EResType::JSON); + return "Text file \"" + node.String() + "\" was not found"; +} + +static std::string musicFile(const JsonNode & node) +{ + TEST_FILE(node.getModScope(), "Music/", node.String(), EResType::SOUND); + TEST_FILE(node.getModScope(), "", node.String(), EResType::SOUND); + return "Music file \"" + node.String() + "\" was not found"; +} + +static std::string soundFile(const JsonNode & node) +{ + TEST_FILE(node.getModScope(), "Sounds/", node.String(), EResType::SOUND); + return "Sound file \"" + node.String() + "\" was not found"; +} + +static std::string animationFile(const JsonNode & node) +{ + return testAnimation(node.String(), node.getModScope()); +} + +static std::string imageFile(const JsonNode & node) +{ + TEST_FILE(node.getModScope(), "Data/", node.String(), EResType::IMAGE); + TEST_FILE(node.getModScope(), "Sprites/", node.String(), EResType::IMAGE); + if (node.String().find(':') != std::string::npos) + return testAnimation(node.String().substr(0, node.String().find(':')), node.getModScope()); + return "Image file \"" + node.String() + "\" was not found"; +} + +static std::string videoFile(const JsonNode & node) +{ + TEST_FILE(node.getModScope(), "Video/", node.String(), EResType::VIDEO); + return "Video file \"" + node.String() + "\" was not found"; +} +#undef TEST_FILE + +JsonValidator::TValidatorMap createCommonFields() +{ + JsonValidator::TValidatorMap ret; + + ret["format"] = formatCheck; + ret["allOf"] = allOfCheck; + ret["anyOf"] = anyOfCheck; + ret["oneOf"] = oneOfCheck; + ret["enum"] = enumCheck; + ret["type"] = typeCheck; + ret["not"] = notCheck; + ret["$ref"] = refCheck; + + // fields that don't need implementation + ret["title"] = emptyCheck; + ret["$schema"] = emptyCheck; + ret["default"] = emptyCheck; + ret["defaultIOS"] = emptyCheck; + ret["defaultAndroid"] = emptyCheck; + ret["defaultWindows"] = emptyCheck; + ret["description"] = emptyCheck; + ret["definitions"] = emptyCheck; + + // Not implemented + ret["propertyNames"] = notImplementedCheck; + ret["contains"] = notImplementedCheck; + ret["const"] = notImplementedCheck; + ret["examples"] = notImplementedCheck; + + return ret; +} + +JsonValidator::TValidatorMap createStringFields() +{ + JsonValidator::TValidatorMap ret = createCommonFields(); + ret["maxLength"] = maxLengthCheck; + ret["minLength"] = minLengthCheck; + + ret["pattern"] = notImplementedCheck; + return ret; +} + +JsonValidator::TValidatorMap createNumberFields() +{ + JsonValidator::TValidatorMap ret = createCommonFields(); + ret["maximum"] = maximumCheck; + ret["minimum"] = minimumCheck; + ret["multipleOf"] = multipleOfCheck; + + ret["exclusiveMaximum"] = exclusiveMaximumCheck; + ret["exclusiveMinimum"] = exclusiveMinimumCheck; + return ret; +} + +JsonValidator::TValidatorMap createVectorFields() +{ + JsonValidator::TValidatorMap ret = createCommonFields(); + ret["items"] = itemsCheck; + ret["minItems"] = minItemsCheck; + ret["maxItems"] = maxItemsCheck; + ret["uniqueItems"] = uniqueItemsCheck; + ret["additionalItems"] = additionalItemsCheck; + return ret; +} + +JsonValidator::TValidatorMap createStructFields() +{ + JsonValidator::TValidatorMap ret = createCommonFields(); + ret["additionalProperties"] = additionalPropertiesCheck; + ret["uniqueProperties"] = uniquePropertiesCheck; + ret["maxProperties"] = maxPropertiesCheck; + ret["minProperties"] = minPropertiesCheck; + ret["dependencies"] = dependenciesCheck; + ret["properties"] = propertiesCheck; + ret["required"] = requiredCheck; + + ret["patternProperties"] = notImplementedCheck; + return ret; +} + +JsonValidator::TFormatMap createFormatMap() +{ + JsonValidator::TFormatMap ret; + ret["textFile"] = textFile; + ret["musicFile"] = musicFile; + ret["soundFile"] = soundFile; + ret["animationFile"] = animationFile; + ret["imageFile"] = imageFile; + ret["videoFile"] = videoFile; + + //TODO: + // uri-reference + // uri-template + // json-pointer + + return ret; +} + +std::string JsonValidator::makeErrorMessage(const std::string &message) +{ + std::string errors; + errors += "At "; + if (!currentPath.empty()) { - Validation::TValidatorMap ret = createCommonFields(); - ret["maxLength"] = String::maxLengthCheck; - ret["minLength"] = String::minLengthCheck; - - ret["pattern"] = Common::notImplementedCheck; - return ret; + for(const JsonNode &path : currentPath) + { + errors += "/"; + if (path.getType() == JsonNode::JsonType::DATA_STRING) + errors += path.String(); + else + errors += std::to_string(static_cast(path.Float())); + } } + else + errors += ""; + errors += "\n\t Error: " + message + "\n"; + return errors; +} - Validation::TValidatorMap createNumberFields() +std::string JsonValidator::check(const std::string & schemaName, const JsonNode & data) +{ + usedSchemas.push_back(schemaName); + auto onscopeExit = vstd::makeScopeGuard([this]() { - Validation::TValidatorMap ret = createCommonFields(); - ret["maximum"] = Number::maximumCheck; - ret["minimum"] = Number::minimumCheck; - ret["multipleOf"] = Number::multipleOfCheck; + usedSchemas.pop_back(); + }); + return check(JsonUtils::getSchema(schemaName), data); +} - ret["exclusiveMaximum"] = Number::exclusiveMaximumCheck; - ret["exclusiveMinimum"] = Number::exclusiveMinimumCheck; - return ret; +std::string JsonValidator::check(const JsonNode & schema, const JsonNode & data) +{ + const TValidatorMap & knownFields = getKnownFieldsFor(data.getType()); + std::string errors; + for(const auto & entry : schema.Struct()) + { + auto checker = knownFields.find(entry.first); + if (checker != knownFields.end()) + errors += checker->second(*this, schema, entry.second, data); } + return errors; +} - Validation::TValidatorMap createVectorFields() +const JsonValidator::TValidatorMap & JsonValidator::getKnownFieldsFor(JsonNode::JsonType type) +{ + static const TValidatorMap commonFields = createCommonFields(); + static const TValidatorMap numberFields = createNumberFields(); + static const TValidatorMap stringFields = createStringFields(); + static const TValidatorMap vectorFields = createVectorFields(); + static const TValidatorMap structFields = createStructFields(); + + switch (type) { - Validation::TValidatorMap ret = createCommonFields(); - ret["items"] = Vector::itemsCheck; - ret["minItems"] = Vector::minItemsCheck; - ret["maxItems"] = Vector::maxItemsCheck; - ret["uniqueItems"] = Vector::uniqueItemsCheck; - ret["additionalItems"] = Vector::additionalItemsCheck; - return ret; - } - - Validation::TValidatorMap createStructFields() - { - Validation::TValidatorMap ret = createCommonFields(); - ret["additionalProperties"] = Struct::additionalPropertiesCheck; - ret["uniqueProperties"] = Struct::uniquePropertiesCheck; - ret["maxProperties"] = Struct::maxPropertiesCheck; - ret["minProperties"] = Struct::minPropertiesCheck; - ret["dependencies"] = Struct::dependenciesCheck; - ret["properties"] = Struct::propertiesCheck; - ret["required"] = Struct::requiredCheck; - - ret["patternProperties"] = Common::notImplementedCheck; - return ret; - } - - Validation::TFormatMap createFormatMap() - { - Validation::TFormatMap ret; - ret["textFile"] = Formats::textFile; - ret["musicFile"] = Formats::musicFile; - ret["soundFile"] = Formats::soundFile; - ret["defFile"] = Formats::defFile; - ret["animationFile"] = Formats::animationFile; - ret["imageFile"] = Formats::imageFile; - ret["videoFile"] = Formats::videoFile; - - //TODO: - // uri-reference - // uri-template - // json-pointer - - return ret; + case JsonNode::JsonType::DATA_FLOAT: + case JsonNode::JsonType::DATA_INTEGER: + return numberFields; + case JsonNode::JsonType::DATA_STRING: return stringFields; + case JsonNode::JsonType::DATA_VECTOR: return vectorFields; + case JsonNode::JsonType::DATA_STRUCT: return structFields; + default: return commonFields; } } -namespace Validation +const JsonValidator::TFormatMap & JsonValidator::getKnownFormats() { - std::string ValidationData::makeErrorMessage(const std::string &message) - { - std::string errors; - errors += "At "; - if (!currentPath.empty()) - { - for(const JsonNode &path : currentPath) - { - errors += "/"; - if (path.getType() == JsonNode::JsonType::DATA_STRING) - errors += path.String(); - else - errors += std::to_string(static_cast(path.Float())); - } - } - else - errors += ""; - errors += "\n\t Error: " + message + "\n"; - return errors; - } - - std::string check(const std::string & schemaName, const JsonNode & data) - { - ValidationData validator; - return check(schemaName, data, validator); - } - - std::string check(const std::string & schemaName, const JsonNode & data, ValidationData & validator) - { - validator.usedSchemas.push_back(schemaName); - auto onscopeExit = vstd::makeScopeGuard([&validator]() - { - validator.usedSchemas.pop_back(); - }); - return check(JsonUtils::getSchema(schemaName), data, validator); - } - - std::string check(const JsonNode & schema, const JsonNode & data, ValidationData & validator) - { - const TValidatorMap & knownFields = getKnownFieldsFor(data.getType()); - std::string errors; - for(const auto & entry : schema.Struct()) - { - auto checker = knownFields.find(entry.first); - if (checker != knownFields.end()) - errors += checker->second(validator, schema, entry.second, data); - } - return errors; - } - - const TValidatorMap & getKnownFieldsFor(JsonNode::JsonType type) - { - static const TValidatorMap commonFields = createCommonFields(); - static const TValidatorMap numberFields = createNumberFields(); - static const TValidatorMap stringFields = createStringFields(); - static const TValidatorMap vectorFields = createVectorFields(); - static const TValidatorMap structFields = createStructFields(); - - switch (type) - { - case JsonNode::JsonType::DATA_FLOAT: - case JsonNode::JsonType::DATA_INTEGER: - return numberFields; - case JsonNode::JsonType::DATA_STRING: return stringFields; - case JsonNode::JsonType::DATA_VECTOR: return vectorFields; - case JsonNode::JsonType::DATA_STRUCT: return structFields; - default: return commonFields; - } - } - - const TFormatMap & getKnownFormats() - { - static const TFormatMap knownFormats = createFormatMap(); - return knownFormats; - } - -} // Validation namespace + static const TFormatMap knownFormats = createFormatMap(); + return knownFormats; +} VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonValidator.h b/lib/json/JsonValidator.h index 5e535daa3..562559dba 100644 --- a/lib/json/JsonValidator.h +++ b/lib/json/JsonValidator.h @@ -13,28 +13,23 @@ VCMI_LIB_NAMESPACE_BEGIN - -//Internal class for Json validation. Mostly compilant with json-schema v6 draft -namespace Validation +/// Class for Json validation. Mostly compilant with json-schema v6 draf +struct JsonValidator { - /// struct used to pass data around during validation - struct ValidationData - { - /// path from root node to current one. - /// JsonNode is used as variant - either string (name of node) or as float (index in list) - std::vector currentPath; + /// path from root node to current one. + /// JsonNode is used as variant - either string (name of node) or as float (index in list) + std::vector currentPath; - /// Stack of used schemas. Last schema is the one used currently. - /// May contain multiple items in case if remote references were found - std::vector usedSchemas; + /// Stack of used schemas. Last schema is the one used currently. + /// May contain multiple items in case if remote references were found + std::vector usedSchemas; - /// generates error message - std::string makeErrorMessage(const std::string &message); - }; + /// generates error message + std::string makeErrorMessage(const std::string &message); using TFormatValidator = std::function; using TFormatMap = std::unordered_map; - using TFieldValidator = std::function; + using TFieldValidator = std::function; using TValidatorMap = std::unordered_map; /// map of known fields in schema @@ -42,8 +37,7 @@ namespace Validation const TFormatMap & getKnownFormats(); std::string check(const std::string & schemaName, const JsonNode & data); - std::string check(const std::string & schemaName, const JsonNode & data, ValidationData & validator); - std::string check(const JsonNode & schema, const JsonNode & data, ValidationData & validator); -} + std::string check(const JsonNode & schema, const JsonNode & data); +}; VCMI_LIB_NAMESPACE_END From 5901bf2190bc07febbc14931b1ab4e34d55b3ad0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Feb 2024 17:50:59 +0200 Subject: [PATCH 157/250] Replace old 'defFile' format with animationFile --- config/schemas/artifact.json | 2 +- config/schemas/creature.json | 6 +++--- config/schemas/hero.json | 2 +- config/schemas/heroClass.json | 4 ++-- config/schemas/objectTemplate.json | 4 ++-- config/schemas/obstacle.json | 2 +- config/schemas/river.json | 2 +- config/schemas/road.json | 2 +- config/schemas/spell.json | 6 +++--- config/schemas/terrain.json | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/config/schemas/artifact.json b/config/schemas/artifact.json index 5a7aed863..ab335a5ca 100644 --- a/config/schemas/artifact.json +++ b/config/schemas/artifact.json @@ -93,7 +93,7 @@ "map" : { "type" : "string", "description" : ".def file for adventure map", - "format" : "defFile" + "format" : "animationFile" } } }, diff --git a/config/schemas/creature.json b/config/schemas/creature.json index d5bc82167..c32fb8fcb 100644 --- a/config/schemas/creature.json +++ b/config/schemas/creature.json @@ -133,12 +133,12 @@ "animation" : { "type" : "string", "description" : "File with animation of this creature in battles", - "format" : "defFile" + "format" : "animationFile" }, "map" : { "type" : "string", "description" : "File with animation of this creature on adventure map", - "format" : "defFile" + "format" : "animationFile" }, "mapMask" : { "type" : "array", @@ -184,7 +184,7 @@ "projectile" : { "type" : "string", "description" : "Path to projectile animation", - "format" : "defFile" + "format" : "animationFile" }, "ray" : { "type" : "array", diff --git a/config/schemas/hero.json b/config/schemas/hero.json index 419836386..148b92ca1 100644 --- a/config/schemas/hero.json +++ b/config/schemas/hero.json @@ -29,7 +29,7 @@ "battleImage" : { "type" : "string", "description" : "Custom animation to be used on battle, overrides hero class property", - "format" : "defFile" + "format" : "animationFile" }, "images" : { "type" : "object", diff --git a/config/schemas/heroClass.json b/config/schemas/heroClass.json index 9efdddaca..b6f5db342 100644 --- a/config/schemas/heroClass.json +++ b/config/schemas/heroClass.json @@ -42,12 +42,12 @@ "female" : { "type" : "string", "description" : "Female version", - "format" : "defFile" + "format" : "animationFile" }, "male" : { "type" : "string", "description" : "Male version", - "format" : "defFile" + "format" : "animationFile" } } } diff --git a/config/schemas/objectTemplate.json b/config/schemas/objectTemplate.json index 9450f5af8..d98f8e8c4 100644 --- a/config/schemas/objectTemplate.json +++ b/config/schemas/objectTemplate.json @@ -9,12 +9,12 @@ "animation" : { "type" : "string", "description" : "Path to def file with animation of this object", - "format" : "defFile" + "format" : "animationFile" }, "editorAnimation" : { "type" : "string", "description" : "Optional path to def file with animation of this object to use in map editor", - "format" : "defFile" + "format" : "animationFile" }, "visitableFrom" : { "type" : "array", diff --git a/config/schemas/obstacle.json b/config/schemas/obstacle.json index bbda0e589..9d245b23d 100644 --- a/config/schemas/obstacle.json +++ b/config/schemas/obstacle.json @@ -45,7 +45,7 @@ "type" : "string", "description" : "Image resource", "anyOf" : [ - { "format" : "defFile" }, + { "format" : "animationFile" }, { "format" : "imageFile" } ] }, diff --git a/config/schemas/river.json b/config/schemas/river.json index db88affaf..0c79b14da 100644 --- a/config/schemas/river.json +++ b/config/schemas/river.json @@ -20,7 +20,7 @@ { "type" : "string", "description" : "Name of file with river graphics", - "format" : "defFile" + "format" : "animationFile" }, "delta" : { diff --git a/config/schemas/road.json b/config/schemas/road.json index 623899ce5..bf9d045da 100644 --- a/config/schemas/road.json +++ b/config/schemas/road.json @@ -20,7 +20,7 @@ { "type" : "string", "description" : "Name of file with road graphics", - "format" : "defFile" + "format" : "animationFile" }, "moveCost" : { diff --git a/config/schemas/spell.json b/config/schemas/spell.json index a94ed65d3..dff3bc2df 100644 --- a/config/schemas/spell.json +++ b/config/schemas/spell.json @@ -15,13 +15,13 @@ { //assumed verticalPosition: top "type" : "string", - "format" : "defFile" + "format" : "animationFile" }, { "type" : "object", "properties" : { "verticalPosition" : {"type" : "string", "enum" :["top","bottom"]}, - "defName" : {"type" : "string", "format" : "defFile"}, + "defName" : {"type" : "string", "format" : "animationFile"}, "effectName" : { "type" : "string" } }, "additionalProperties" : false @@ -41,7 +41,7 @@ "items" : { "type" : "object", "properties" : { - "defName" : {"type" : "string", "format" : "defFile"}, + "defName" : {"type" : "string", "format" : "animationFile"}, "minimumAngle" : {"type" : "number", "minimum" : 0} }, "additionalProperties" : false diff --git a/config/schemas/terrain.json b/config/schemas/terrain.json index bc518106b..23aa598a3 100644 --- a/config/schemas/terrain.json +++ b/config/schemas/terrain.json @@ -35,7 +35,7 @@ { "type" : "string", "description" : "Name of file with graphicks", - "format" : "defFile" + "format" : "animationFile" }, "rockTerrain" : { From f86708bf3704d6c85528da58d63216eb9fc8a869 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Feb 2024 17:56:58 +0200 Subject: [PATCH 158/250] Fix json5 parsing --- lib/json/JsonParser.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/json/JsonParser.cpp b/lib/json/JsonParser.cpp index e43c31c22..d86b3a19f 100644 --- a/lib/json/JsonParser.cpp +++ b/lib/json/JsonParser.cpp @@ -81,6 +81,7 @@ bool JsonParser::extractValue(JsonNode & node) switch(input[pos]) { case '\"': + case '\'': return extractString(node); case 'n': return extractNull(node); @@ -93,6 +94,8 @@ bool JsonParser::extractValue(JsonNode & node) case '[': return extractArray(node); case '-': + case '+': + case '.': return extractFloat(node); default: { From 57eece233bac710340e0ef43903affab62334e2f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 20 Feb 2024 19:01:06 +0200 Subject: [PATCH 159/250] Ignore BOM at the start of json file --- lib/json/JsonParser.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/json/JsonParser.cpp b/lib/json/JsonParser.cpp index d86b3a19f..e4c5f6f8c 100644 --- a/lib/json/JsonParser.cpp +++ b/lib/json/JsonParser.cpp @@ -40,6 +40,11 @@ JsonNode JsonParser::parse(const std::string & fileName) if(!TextOperations::isValidUnicodeString(input.data(), input.size())) error("Not a valid UTF-8 file", false); + // If file starts with BOM - skip it + uint32_t firstCharacter = TextOperations::getUnicodeCodepoint(input.data(), input.size()); + if (firstCharacter == 0xFEFF) + pos += TextOperations::getUnicodeCharacterSize(input[0]); + extractValue(root); extractWhitespace(false); From 2549f626614c9d055ab9a61c9475a12398dbc9a8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Feb 2024 14:57:23 +0200 Subject: [PATCH 160/250] Added missing field to schema --- config/schemas/hero.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/schemas/hero.json b/config/schemas/hero.json index 148b92ca1..8d533387b 100644 --- a/config/schemas/hero.json +++ b/config/schemas/hero.json @@ -31,6 +31,13 @@ "description" : "Custom animation to be used on battle, overrides hero class property", "format" : "animationFile" }, + "compatibilityIdentifiers" : { + "type" : "array", + "items" : { + "type" : "string", + }, + "description" : "Additional identifiers that may refer to this object, to provide compatibility after object has been renamed" + }, "images" : { "type" : "object", "additionalProperties" : false, From 5419df1140a090d67a7a779636542b94593cbbab Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Feb 2024 15:48:15 +0200 Subject: [PATCH 161/250] Removed no longer used class --- client/windows/InfoWindows.cpp | 78 ---------------------------------- client/windows/InfoWindows.h | 15 ------- 2 files changed, 93 deletions(-) diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index bc55dbb72..85591fad1 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -232,84 +232,6 @@ std::string CInfoWindow::genText(std::string title, std::string description) return std::string("{") + title + "}" + "\n\n" + description; } -CInfoPopup::CInfoPopup(SDL_Surface * Bitmap, int x, int y, bool Free) - :free(Free),bitmap(Bitmap) -{ - init(x, y); -} - - -CInfoPopup::CInfoPopup(SDL_Surface * Bitmap, const Point &p, ETextAlignment alignment, bool Free) - : free(Free),bitmap(Bitmap) -{ - switch(alignment) - { - case ETextAlignment::BOTTOMRIGHT: - init(p.x - Bitmap->w, p.y - Bitmap->h); - break; - case ETextAlignment::CENTER: - init(p.x - Bitmap->w/2, p.y - Bitmap->h/2); - break; - case ETextAlignment::TOPLEFT: - init(p.x, p.y); - break; - case ETextAlignment::TOPCENTER: - init(p.x - Bitmap->w/2, p.y); - break; - default: - assert(0); //not implemented - } -} - -CInfoPopup::CInfoPopup(SDL_Surface *Bitmap, bool Free) -{ - CCS->curh->hide(); - - free=Free; - bitmap=Bitmap; - - if(bitmap) - { - pos.x = GH.screenDimensions().x / 2 - bitmap->w / 2; - pos.y = GH.screenDimensions().y / 2 - bitmap->h / 2; - pos.h = bitmap->h; - pos.w = bitmap->w; - } -} - -void CInfoPopup::close() -{ - if(free) - SDL_FreeSurface(bitmap); - WindowBase::close(); -} - -void CInfoPopup::show(Canvas & to) -{ - CSDL_Ext::blitAt(bitmap,pos.x,pos.y,to.getInternalSurface()); -} - -CInfoPopup::~CInfoPopup() -{ - CCS->curh->show(); -} - -void CInfoPopup::init(int x, int y) -{ - CCS->curh->hide(); - - pos.x = x; - pos.y = y; - pos.h = bitmap->h; - pos.w = bitmap->w; - - // Put the window back on screen if necessary - vstd::amax(pos.x, 0); - vstd::amax(pos.y, 0); - vstd::amin(pos.x, GH.screenDimensions().x - bitmap->w); - vstd::amin(pos.y, GH.screenDimensions().y - bitmap->h); -} - bool CRClickPopup::isPopupWindow() const { return true; diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h index d79ed40f3..4765d70ed 100644 --- a/client/windows/InfoWindows.h +++ b/client/windows/InfoWindows.h @@ -97,21 +97,6 @@ public: virtual ~CRClickPopupInt(); }; -class CInfoPopup : public CRClickPopup -{ -public: - bool free; //TODO: comment me - SDL_Surface * bitmap; //popup background - void close() override; - void show(Canvas & to) override; - CInfoPopup(SDL_Surface * Bitmap, int x, int y, bool Free=false); - CInfoPopup(SDL_Surface * Bitmap, const Point &p, ETextAlignment alignment, bool Free=false); - CInfoPopup(SDL_Surface * Bitmap = nullptr, bool Free = false); - - void init(int x, int y); - ~CInfoPopup(); -}; - /// popup on adventure map for town\hero and other objects with customized popup content class CInfoBoxPopup : public CWindowObject { From 658cc14cd091dbccebc70d40bb8802a261839054 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Feb 2024 15:48:55 +0200 Subject: [PATCH 162/250] Removed direct usage of SDL_Surface in info windows --- client/windows/CMessage.cpp | 39 +++++++++++++++----------- client/windows/InfoWindows.cpp | 51 +++++++++++++--------------------- client/windows/InfoWindows.h | 17 +++--------- 3 files changed, 46 insertions(+), 61 deletions(-) diff --git a/client/windows/CMessage.cpp b/client/windows/CMessage.cpp index 5484a9425..7b5c26612 100644 --- a/client/windows/CMessage.cpp +++ b/client/windows/CMessage.cpp @@ -16,6 +16,7 @@ #include "../../lib/TextOperations.h" #include "../windows/InfoWindows.h" +#include "../widgets/Images.h" #include "../widgets/Buttons.h" #include "../widgets/CComponent.h" #include "../widgets/Slider.h" @@ -266,17 +267,22 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play if(dynamic_cast(ret)) //it's selection window, so we'll blit "or" between components blitOr = true; - const int sizes[][2] = {{400, 125}, {500, 150}, {600, 200}, {480, 400}}; + constexpr std::array sizes = { + Point(400, 125), + Point(500, 150), + Point(600, 200), + Point(480, 400) + }; assert(ret && ret->text); for(int i = 0; i < std::size(sizes) - && sizes[i][0] < GH.screenDimensions().x - 150 - && sizes[i][1] < GH.screenDimensions().y - 150 + && sizes[i].x < GH.screenDimensions().x - 150 + && sizes[i].y < GH.screenDimensions().y - 150 && ret->text->slider; i++) { - ret->text->resize(Point(sizes[i][0], sizes[i][1])); + ret->text->resize(sizes[i]); } if(ret->text->slider) @@ -316,34 +322,35 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play vstd::amin(winSize.first, GH.screenDimensions().x - 150); - ret->bitmap = drawDialogBox (winSize.first + 2*SIDE_MARGIN, winSize.second + 2*SIDE_MARGIN, player); - ret->pos.h=ret->bitmap->h; - ret->pos.w=ret->bitmap->w; + ret->pos.h = winSize.second + 2 * SIDE_MARGIN; + ret->pos.w = winSize.first + 2 * SIDE_MARGIN; ret->center(); + ret->backgroundTexture->pos = ret->pos; int curh = SIDE_MARGIN; int xOffset = (ret->pos.w - ret->text->pos.w)/2; if(!ret->buttons.size() && !ret->components.size()) //improvement for very small text only popups -> center text vertically { - if(ret->bitmap->h > ret->text->pos.h + 2*SIDE_MARGIN) - curh = (ret->bitmap->h - ret->text->pos.h)/2; + if(ret->pos.h > ret->text->pos.h + 2*SIDE_MARGIN) + curh = (ret->pos.h - ret->text->pos.h)/2; } ret->text->moveBy(Point(xOffset, curh)); curh += ret->text->pos.h; - if (ret->components.size()) - { - curh += BEFORE_COMPONENTS; - comps.blitCompsOnSur (blitOr, BETWEEN_COMPS, curh, ret->bitmap); - } + //if (ret->components.size()) + //{ + // curh += BEFORE_COMPONENTS; + // comps.blitCompsOnSur (blitOr, BETWEEN_COMPS, curh, ret->bitmap); + //} + if(ret->buttons.size()) { // Position the buttons at the bottom of the window - bw = (ret->bitmap->w/2) - (bw/2); - curh = ret->bitmap->h - SIDE_MARGIN - ret->buttons[0]->pos.h; + bw = (ret->pos.w/2) - (bw/2); + curh = ret->pos.h - SIDE_MARGIN - ret->buttons[0]->pos.h; for(auto & elem : ret->buttons) { diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index 85591fad1..c726ff6fa 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -15,20 +15,21 @@ #include "../CPlayerInterface.h" #include "../CMusicHandler.h" -#include "../widgets/CComponent.h" -#include "../widgets/MiscWidgets.h" -#include "../widgets/Buttons.h" -#include "../widgets/TextControls.h" -#include "../gui/CGuiHandler.h" -#include "../gui/WindowHandler.h" +#include "../adventureMap/AdventureMapInterface.h" #include "../battle/BattleInterface.h" #include "../battle/BattleInterfaceClasses.h" -#include "../adventureMap/AdventureMapInterface.h" -#include "../windows/CMessage.h" -#include "../render/Canvas.h" -#include "../renderSDL/SDL_Extensions.h" +#include "../gui/CGuiHandler.h" #include "../gui/CursorHandler.h" #include "../gui/Shortcut.h" +#include "../gui/WindowHandler.h" +#include "../render/Canvas.h" +#include "../renderSDL/SDL_Extensions.h" +#include "../widgets/Buttons.h" +#include "../widgets/CComponent.h" +#include "../widgets/Images.h" +#include "../widgets/MiscWidgets.h" +#include "../widgets/TextControls.h" +#include "../windows/CMessage.h" #include "../../CCallback.h" @@ -43,20 +44,6 @@ #include -void CSimpleWindow::show(Canvas & to) -{ - if(bitmap) - CSDL_Ext::blitAt(bitmap, pos.x, pos.y, to.getInternalSurface()); -} -CSimpleWindow::~CSimpleWindow() -{ - if (bitmap) - { - SDL_FreeSurface(bitmap); - bitmap=nullptr; - } -} - void CSelWindow::selectionChange(unsigned to) { for (unsigned i=0;i> & comps, const std::vector > > &Buttons, QueryID askID) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + + backgroundTexture = std::make_shared(ImagePath::builtin("DiBoxBck"), pos); + ID = askID; for (int i = 0; i < Buttons.size(); i++) { @@ -138,6 +128,8 @@ CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + backgroundTexture = std::make_shared(ImagePath::builtin("DiBoxBck"), pos); + ID = QueryID(-1); for(auto & Button : Buttons) { @@ -188,19 +180,14 @@ void CInfoWindow::close() LOCPLINT->showingDialog->setn(false); } -void CInfoWindow::show(Canvas & to) +void CInfoWindow::showAll(Canvas & to) { - CIntObject::show(to); + CIntObject::showAll(to); + CMessage::drawBorder(LOCPLINT ? LOCPLINT->playerID : PlayerColor(1), to.getInternalSurface(), pos.w, pos.h, pos.x, pos.y); } CInfoWindow::~CInfoWindow() = default; -void CInfoWindow::showAll(Canvas & to) -{ - CSimpleWindow::show(to); - CIntObject::showAll(to); -} - void CInfoWindow::showInfoDialog(const std::string &text, const TCompsInfo & components, PlayerColor player) { GH.windows().pushWindow(CInfoWindow::create(text, player, components)); diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h index 4765d70ed..c871cc400 100644 --- a/client/windows/InfoWindows.h +++ b/client/windows/InfoWindows.h @@ -34,32 +34,23 @@ class CTextBox; class CButton; class CSlider; class CArmyTooltip; - -// Window GUI class -class CSimpleWindow : public WindowBase -{ -public: - SDL_Surface * bitmap; //background - void show(Canvas & to) override; - CSimpleWindow():bitmap(nullptr){}; - virtual ~CSimpleWindow(); -}; +class CFilledTexture; /// text + comp. + ok button -class CInfoWindow : public CSimpleWindow +class CInfoWindow : public WindowBase { public: using TButtonsInfo = std::vector>>; using TCompsInfo = std::vector>; QueryID ID; //for identification + std::shared_ptr backgroundTexture; std::shared_ptr text; std::vector> buttons; TCompsInfo components; void close() override; - - void show(Canvas & to) override; void showAll(Canvas & to) override; + void sliderMoved(int to); CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo & comps = TCompsInfo(), const TButtonsInfo & Buttons = TButtonsInfo()); From e6b339448fcf972f44e5ed3e3ad557efb93284c8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Feb 2024 18:32:15 +0200 Subject: [PATCH 163/250] Refactoring & deduplication of infowindow code --- client/battle/BattleWindow.cpp | 2 +- client/widgets/TextControls.cpp | 11 + client/widgets/TextControls.h | 3 + client/windows/CHeroBackpackWindow.cpp | 4 +- client/windows/CMessage.cpp | 307 +++--------------- client/windows/CMessage.h | 3 +- client/windows/CWindowObject.cpp | 2 +- client/windows/InfoWindows.cpp | 48 +-- client/windows/InfoWindows.h | 4 +- .../windows/settings/SettingsMainWindow.cpp | 2 +- 10 files changed, 75 insertions(+), 311 deletions(-) diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index b12e6819c..4a0365e79 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -813,7 +813,7 @@ void BattleWindow::showAll(Canvas & to) CIntObject::showAll(to); if (GH.screenDimensions().x != 800 || GH.screenDimensions().y !=600) - CMessage::drawBorder(owner.curInt->playerID, to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15); + CMessage::drawBorder(owner.curInt->playerID, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15); } void BattleWindow::show(Canvas & to) diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index 1e4e1efd0..32b477818 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -369,6 +369,17 @@ void CTextBox::sliderMoved(int to) label->scrollTextTo(to); } +void CTextBox::trimToFit() +{ + if (slider) + return; + + pos.w = label->textSize.x; + pos.h = label->textSize.y; + label->pos.w = label->textSize.x; + label->pos.h = label->textSize.y; +} + void CTextBox::resize(Point newSize) { pos.w = newSize.x; diff --git a/client/widgets/TextControls.h b/client/widgets/TextControls.h index dce169a93..d9ce3dab4 100644 --- a/client/widgets/TextControls.h +++ b/client/widgets/TextControls.h @@ -116,6 +116,9 @@ public: CTextBox(std::string Text, const Rect & rect, int SliderStyle, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::TOPLEFT, const ColorRGBA & Color = Colors::WHITE); void resize(Point newSize); + /// Resizes text box to minimal size needed to fit current text + /// No effect if text is too large to fit and requires slider + void trimToFit(); void setText(const std::string & Txt); void sliderMoved(int to); }; diff --git a/client/windows/CHeroBackpackWindow.cpp b/client/windows/CHeroBackpackWindow.cpp index 608465b5e..692863cad 100644 --- a/client/windows/CHeroBackpackWindow.cpp +++ b/client/windows/CHeroBackpackWindow.cpp @@ -45,7 +45,7 @@ CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero) void CHeroBackpackWindow::showAll(Canvas & to) { CIntObject::showAll(to); - CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15); + CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to, pos.w+28, pos.h+29, pos.x-14, pos.y-15); } CHeroQuickBackpackWindow::CHeroQuickBackpackWindow(const CGHeroInstance * hero, ArtifactPosition targetSlot) @@ -87,6 +87,6 @@ void CHeroQuickBackpackWindow::showAll(Canvas & to) close(); return; } - CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to.getInternalSurface(), pos.w + 28, pos.h + 29, pos.x - 14, pos.y - 15); + CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to, pos.w + 28, pos.h + 29, pos.x - 14, pos.y - 15); CIntObject::showAll(to); } diff --git a/client/windows/CMessage.cpp b/client/windows/CMessage.cpp index 7b5c26612..8bf429a77 100644 --- a/client/windows/CMessage.cpp +++ b/client/windows/CMessage.cpp @@ -30,51 +30,13 @@ #include "../render/IFont.h" #include "../renderSDL/SDL_Extensions.h" -#include - -const int BETWEEN_COMPS_ROWS = 10; const int BEFORE_COMPONENTS = 30; -const int BETWEEN_COMPS = 30; const int SIDE_MARGIN = 30; -template std::pair max(const std::pair &x, const std::pair &y) -{ - std::pair ret; - ret.first = std::max(x.first,y.first); - ret.second = std::max(x.second,y.second); - return ret; -} - -//One image component + subtitles below it -class ComponentResolved : public CIntObject -{ -public: - std::shared_ptr comp; - - //blit component with image centered at this position - void showAll(Canvas & to) override; - - //ComponentResolved(); - ComponentResolved(std::shared_ptr Comp); - ~ComponentResolved(); -}; -// Full set of components for blitting on dialog box -struct ComponentsToBlit -{ - std::vector< std::vector>> comps; - int w, h; - - void blitCompsOnSur(bool blitOr, int inter, int &curh, SDL_Surface *ret); - ComponentsToBlit(std::vector> & SComps, int maxw, bool blitOr); - ~ComponentsToBlit(); -}; - namespace { std::array, PlayerColor::PLAYER_LIMIT_I> dialogBorders; std::array>, PlayerColor::PLAYER_LIMIT_I> piecesOfBox; - - std::shared_ptr background;//todo: should be CFilledTexture } void CMessage::init() @@ -93,8 +55,6 @@ void CMessage::init() piecesOfBox[i].push_back(image); } } - - background = GH.renderHandler().loadImage(ImagePath::builtin("DIBOXBCK.BMP"), EImageBlitMode::OPAQUE); } void CMessage::dispose() @@ -103,22 +63,6 @@ void CMessage::dispose() item.reset(); } -SDL_Surface * CMessage::drawDialogBox(int w, int h, PlayerColor playerColor) -{ - //prepare surface - SDL_Surface * ret = CSDL_Ext::newSurface(w,h); - for (int i=0; iwidth())//background - { - for (int j=0; jheight()) - { - background->draw(ret, i, j); - } - } - - drawBorder(playerColor, ret, w, h); - return ret; -} - std::vector CMessage::breakText( std::string text, size_t maxLineWidth, EFonts font ) { assert(maxLineWidth != 0); @@ -157,7 +101,7 @@ std::vector CMessage::breakText( std::string text, size_t maxLineWi opened=true; std::smatch match; - std::regex expr("^\\{(.*?)\\|"); + std::regex expr("^\\{(.*?)\\|"); std::string tmp = text.substr(currPos); if(std::regex_search(tmp, match, expr)) { @@ -263,42 +207,30 @@ int CMessage::getEstimatedComponentHeight(int numComps) void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor player) { - bool blitOr = false; - if(dynamic_cast(ret)) //it's selection window, so we'll blit "or" between components - blitOr = true; - - constexpr std::array sizes = { - Point(400, 125), - Point(500, 150), - Point(600, 200), - Point(480, 400) + constexpr std::array textAreaSizes = { + Point(300, 200), // if message is small, h3 will use 300px-wide text box with up to 200px height + Point(400, 200), // once text no longer fits into 300x200 box, h3 will start using 400px - wide boxes + Point(600, 200) // if 400px is not enough either, h3 will use largest, 600px-wide textbox, potentially with slider }; assert(ret && ret->text); - for(int i = 0; - i < std::size(sizes) - && sizes[i].x < GH.screenDimensions().x - 150 - && sizes[i].y < GH.screenDimensions().y - 150 - && ret->text->slider; - i++) + + for (auto const & area : textAreaSizes) { - ret->text->resize(sizes[i]); + ret->text->resize(area); + if (!ret->text->slider) + break; // suitable size found, use it } if(ret->text->slider) - { ret->text->slider->addUsedEvents(CIntObject::WHEEL | CIntObject::KEYBOARD); - } - else - { - ret->text->resize(ret->text->label->textSize + Point(10, 10)); - } - std::pair winSize(ret->text->pos.w, ret->text->pos.h); //start with text size + ret->text->trimToFit(); - ComponentsToBlit comps(ret->components,500, blitOr); - if (ret->components.size()) - winSize.second += 10 + comps.h; //space to first component + Point winSize(ret->text->pos.w, ret->text->pos.h); //start with text size + + if (ret->components) + winSize.y += 10 + ret->components->pos.h; //space to first component int bw = 0; if (ret->buttons.size()) @@ -311,26 +243,27 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play bw+=elem->pos.w; vstd::amax(bh, elem->pos.h); } - winSize.second += 20 + bh;//before button + button + winSize.y += 20 + bh;//before button + button } // Clip window size - vstd::amax(winSize.second, 50); - vstd::amax(winSize.first, 80); - vstd::amax(winSize.first, comps.w); - vstd::amax(winSize.first, bw); + vstd::amax(winSize.y, 50); + vstd::amax(winSize.x, 80); + if (ret->components) + vstd::amax(winSize.x, ret->components->pos.w); + vstd::amax(winSize.x, bw); - vstd::amin(winSize.first, GH.screenDimensions().x - 150); + vstd::amin(winSize.x, GH.screenDimensions().x - 150); - ret->pos.h = winSize.second + 2 * SIDE_MARGIN; - ret->pos.w = winSize.first + 2 * SIDE_MARGIN; + ret->pos.h = winSize.y + 2 * SIDE_MARGIN; + ret->pos.w = winSize.x + 2 * SIDE_MARGIN; ret->center(); ret->backgroundTexture->pos = ret->pos; int curh = SIDE_MARGIN; int xOffset = (ret->pos.w - ret->text->pos.w)/2; - if(!ret->buttons.size() && !ret->components.size()) //improvement for very small text only popups -> center text vertically + if(ret->buttons.empty() && !ret->components) //improvement for very small text only popups -> center text vertically { if(ret->pos.h > ret->text->pos.h + 2*SIDE_MARGIN) curh = (ret->pos.h - ret->text->pos.h)/2; @@ -340,11 +273,11 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play curh += ret->text->pos.h; - //if (ret->components.size()) - //{ - // curh += BEFORE_COMPONENTS; - // comps.blitCompsOnSur (blitOr, BETWEEN_COMPS, curh, ret->bitmap); - //} + if (ret->components) + { + curh += BEFORE_COMPONENTS; + curh += ret->components->pos.h; + } if(ret->buttons.size()) { @@ -358,11 +291,11 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play bw += elem->pos.w + 20; } } - for(size_t i=0; icomponents.size(); i++) - ret->components[i]->moveBy(Point(ret->pos.x, ret->pos.y)); + if (ret->components) + ret->components->moveBy(Point(ret->pos.x, ret->pos.y)); } -void CMessage::drawBorder(PlayerColor playerColor, SDL_Surface * ret, int w, int h, int x, int y) +void CMessage::drawBorder(PlayerColor playerColor, Canvas & to, int w, int h, int x, int y) { if(playerColor.isSpectator()) playerColor = PlayerColor(1); @@ -375,20 +308,13 @@ void CMessage::drawBorder(PlayerColor playerColor, SDL_Surface * ret, int w, int const int stop_x = x + w - box[1]->width(); const int bottom_y = y+h-box[7]->height()+1; while (start_x < stop_x) { - int cur_w = stop_x - start_x; - if (cur_w > box[6]->width()) - cur_w = box[6]->width(); // Top border - Rect srcR(0, 0, cur_w, box[6]->height()); - Rect dstR(start_x, y, 0, 0); - box[6]->draw(ret, &dstR, &srcR); - + to.draw(box[6], Point(start_x, y)); // Bottom border - dstR.y = bottom_y; - box[7]->draw(ret, &dstR, &srcR); + to.draw(box[7], Point(start_x, bottom_y)); - start_x += cur_w; + start_x += box[6]->width(); } // Vertical borders @@ -396,165 +322,18 @@ void CMessage::drawBorder(PlayerColor playerColor, SDL_Surface * ret, int w, int const int stop_y = y + h - box[2]->height()+1; const int right_x = x+w-box[5]->width(); while (start_y < stop_y) { - int cur_h = stop_y - start_y; - if (cur_h > box[4]->height()) - cur_h = box[4]->height(); // Left border - Rect srcR(0, 0, box[4]->width(), cur_h); - Rect dstR(x, start_y, 0, 0); - box[4]->draw(ret, &dstR, &srcR); - + to.draw(box[4], Point(x, start_y)); // Right border - dstR.x = right_x; - box[5]->draw(ret, &dstR, &srcR); + to.draw(box[5], Point(right_x, start_y)); - start_y += cur_h; + start_y += box[4]->height(); } //corners - Rect dstR(x, y, box[0]->width(), box[0]->height()); - box[0]->draw(ret, &dstR, nullptr); - - dstR=Rect(x+w-box[1]->width(), y, box[1]->width(), box[1]->height()); - box[1]->draw(ret, &dstR, nullptr); - - dstR=Rect(x, y+h-box[2]->height()+1, box[2]->width(), box[2]->height()); - box[2]->draw(ret, &dstR, nullptr); - - dstR=Rect(x+w-box[3]->width(), y+h-box[3]->height()+1, box[3]->width(), box[3]->height()); - box[3]->draw(ret, &dstR, nullptr); -} - -ComponentResolved::ComponentResolved(std::shared_ptr Comp): - comp(Comp) -{ - //Temporary assign ownership on comp - if (parent) - parent->removeChild(this); - if (comp->parent) - { - comp->parent->addChild(this); - comp->parent->removeChild(comp.get()); - } - - addChild(comp.get()); - defActions = 255 - DISPOSE; - pos.x = pos.y = 0; - - pos.w = comp->pos.w; - pos.h = comp->pos.h; -} - -ComponentResolved::~ComponentResolved() -{ - if (parent) - { - removeChild(comp.get()); - parent->addChild(comp.get()); - } -} - -void ComponentResolved::showAll(Canvas & to) -{ - CIntObject::showAll(to); - comp->showAll(to); -} - -ComponentsToBlit::~ComponentsToBlit() = default; - -ComponentsToBlit::ComponentsToBlit(std::vector> & SComps, int maxw, bool blitOr) -{ - int orWidth = static_cast(graphics->fonts[FONT_MEDIUM]->getStringWidth(CGI->generaltexth->allTexts[4])); - - w = h = 0; - if(SComps.empty()) - return; - - comps.resize(1); - int curw = 0; - int curr = 0; //current row - - for(auto & SComp : SComps) - { - auto cur = std::make_shared(SComp); - - int toadd = (cur->pos.w + BETWEEN_COMPS + (blitOr ? orWidth : 0)); - if (curw + toadd > maxw) - { - curr++; - vstd::amax(w,curw); - curw = cur->pos.w; - comps.resize(curr+1); - } - else - { - curw += toadd; - vstd::amax(w,curw); - } - - comps[curr].push_back(cur); - } - - for(auto & elem : comps) - { - int maxHeight = 0; - for(size_t j=0;jpos.h); - - h += maxHeight + BETWEEN_COMPS_ROWS; - } -} - -void ComponentsToBlit::blitCompsOnSur( bool blitOr, int inter, int &curh, SDL_Surface *ret ) -{ - int orWidth = static_cast(graphics->fonts[FONT_MEDIUM]->getStringWidth(CGI->generaltexth->allTexts[4])); - - for (auto & elem : comps)//for each row - { - int totalw=0, maxHeight=0; - for(size_t j=0;jpos.w; - vstd::amax(maxHeight, cur->pos.h); - } - - //add space between comps in this row - if(blitOr) - totalw += (inter*2+orWidth) * ((int)elem.size() - 1); - else - totalw += (inter) * ((int)elem.size() - 1); - - int middleh = curh + maxHeight/2;//axis for image aligment - int curw = ret->w/2 - totalw/2; - - for(size_t j=0;jmoveTo(Point(curw, curh)); - - //blit component - Canvas canvas = Canvas::createFromSurface(ret); - - cur->showAll(canvas); - curw += cur->pos.w; - - //if there is subsequent component blit "or" - if(j<(elem.size()-1)) - { - if(blitOr) - { - curw+=inter; - - graphics->fonts[FONT_MEDIUM]->renderTextLeft(ret, CGI->generaltexth->allTexts[4], Colors::WHITE, - Point(curw,middleh-((int)graphics->fonts[FONT_MEDIUM]->getLineHeight()/2))); - - curw+=orWidth; - } - curw+=inter; - } - } - curh += maxHeight + BETWEEN_COMPS_ROWS; - } + to.draw(box[0], Point(x,y)); + to.draw(box[1], Point(x+w-box[1]->width(), y)); + to.draw(box[2], Point(x, y+h-box[2]->height()+1)); + to.draw(box[3], Point(x+w-box[3]->width(), y+h-box[3]->height()+1)); } diff --git a/client/windows/CMessage.h b/client/windows/CMessage.h index 84bbfada8..67b8d5b47 100644 --- a/client/windows/CMessage.h +++ b/client/windows/CMessage.h @@ -15,6 +15,7 @@ struct SDL_Surface; class CInfoWindow; class CComponent; +class Canvas; VCMI_LIB_NAMESPACE_BEGIN class ColorRGBA; @@ -29,7 +30,7 @@ class CMessage public: /// Draw border on exiting surface - static void drawBorder(PlayerColor playerColor, SDL_Surface * ret, int w, int h, int x=0, int y=0); + static void drawBorder(PlayerColor playerColor, Canvas & to, int w, int h, int x, int y); static void drawIWindow(CInfoWindow * ret, std::string text, PlayerColor player); diff --git a/client/windows/CWindowObject.cpp b/client/windows/CWindowObject.cpp index 231213219..bf6845d07 100644 --- a/client/windows/CWindowObject.cpp +++ b/client/windows/CWindowObject.cpp @@ -232,7 +232,7 @@ void CWindowObject::showAll(Canvas & to) CIntObject::showAll(to); if ((options & BORDERED) && (pos.dimensions() != GH.screenDimensions())) - CMessage::drawBorder(color, to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15); + CMessage::drawBorder(color, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15); } bool CWindowObject::isPopupWindow() const diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index c726ff6fa..d9ab69fcd 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -42,20 +42,6 @@ #include "../../lib/mapObjects/MiscObjects.h" #include "../../lib/gameState/InfoAboutArmy.h" -#include - -void CSelWindow::selectionChange(unsigned to) -{ - for (unsigned i=0;i(components[i]); - if (!pom) - continue; - pom->select(i==to); - } - redraw(); -} - CSelWindow::CSelWindow(const std::string &Text, PlayerColor player, int charperline, const std::vector> & comps, const std::vector > > &Buttons, QueryID askID) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); @@ -90,16 +76,9 @@ CSelWindow::CSelWindow(const std::string &Text, PlayerColor player, int charperl buttons.back()->assignedKey = EShortcut::GLOBAL_CANCEL; } - for(int i=0;irecActions = 255-DISPOSE; - addChild(comps[i].get()); - components.push_back(comps[i]); - comps[i]->onSelect = std::bind(&CSelWindow::selectionChange,this,i); - comps[i]->onChoose = std::bind(&CSelWindow::madeChoiceAndClose,this); - if(i<8) - comps[i]->assignedKey = vstd::next(EShortcut::SELECT_INDEX_1,i); - } + if (!comps.empty()) + components = std::make_shared(comps, Rect(0,0,600,300)); + CMessage::drawIWindow(this, Text, player); } @@ -108,13 +87,9 @@ void CSelWindow::madeChoice() if(ID.getNum() < 0) return; int ret = -1; - for (int i=0;i(components[i])->selected) - { - ret = i; - } - } + if (components) + ret = components->selectedIndex(); + LOCPLINT->cb->selectionMade(ret+1,ID); } @@ -156,13 +131,8 @@ CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo buttons.back()->assignedKey = EShortcut::GLOBAL_CANCEL; } - for(auto & comp : comps) - { - comp->recActions = 0xff & ~DISPOSE; - addChild(comp.get()); - comp->recActions &= ~(SHOWALL | UPDATE); - components.push_back(comp); - } + if (!comps.empty()) + components = std::make_shared(comps, Rect(0,0,600,300)); CMessage::drawIWindow(this,Text,player); } @@ -183,7 +153,7 @@ void CInfoWindow::close() void CInfoWindow::showAll(Canvas & to) { CIntObject::showAll(to); - CMessage::drawBorder(LOCPLINT ? LOCPLINT->playerID : PlayerColor(1), to.getInternalSurface(), pos.w, pos.h, pos.x, pos.y); + CMessage::drawBorder(LOCPLINT ? LOCPLINT->playerID : PlayerColor(1), to, pos.w, pos.h, pos.x, pos.y); } CInfoWindow::~CInfoWindow() = default; diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h index c871cc400..67cd75b49 100644 --- a/client/windows/InfoWindows.h +++ b/client/windows/InfoWindows.h @@ -29,6 +29,7 @@ class CAnimImage; class CLabel; class CAnimation; class CComponent; +class CComponentBox; class CSelectableComponent; class CTextBox; class CButton; @@ -45,8 +46,8 @@ public: QueryID ID; //for identification std::shared_ptr backgroundTexture; std::shared_ptr text; + std::shared_ptr components; std::vector> buttons; - TCompsInfo components; void close() override; void showAll(Canvas & to) override; @@ -104,7 +105,6 @@ public: class CSelWindow : public CInfoWindow { public: - void selectionChange(unsigned to); void madeChoice(); //looks for selected component and calls callback void madeChoiceAndClose(); CSelWindow(const std::string & text, PlayerColor player, int charperline, const std::vector> & comps, const std::vector > > &Buttons, QueryID askID); diff --git a/client/windows/settings/SettingsMainWindow.cpp b/client/windows/settings/SettingsMainWindow.cpp index b9ccb0815..f5c3ab7ac 100644 --- a/client/windows/settings/SettingsMainWindow.cpp +++ b/client/windows/settings/SettingsMainWindow.cpp @@ -185,7 +185,7 @@ void SettingsMainWindow::showAll(Canvas & to) color = PlayerColor(1); // TODO: Spectator shouldn't need special code for UI colors CIntObject::showAll(to); - CMessage::drawBorder(color, to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15); + CMessage::drawBorder(color, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15); } void SettingsMainWindow::onScreenResize() From 4b4dedeab957e23e23d75fa9568df9740939f224 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Feb 2024 18:53:12 +0200 Subject: [PATCH 164/250] Code formatting --- client/windows/CMessage.cpp | 138 ++++++++++++++++----------------- client/windows/CMessage.h | 1 - client/windows/InfoWindows.cpp | 107 ++++++++++++------------- client/windows/InfoWindows.h | 17 ++-- 4 files changed, 122 insertions(+), 141 deletions(-) diff --git a/client/windows/CMessage.cpp b/client/windows/CMessage.cpp index 8bf429a77..ce78a2c70 100644 --- a/client/windows/CMessage.cpp +++ b/client/windows/CMessage.cpp @@ -11,42 +11,36 @@ #include "StdInc.h" #include "CMessage.h" -#include "../CGameInfo.h" -#include "../../lib/CGeneralTextHandler.h" #include "../../lib/TextOperations.h" -#include "../windows/InfoWindows.h" -#include "../widgets/Images.h" -#include "../widgets/Buttons.h" -#include "../widgets/CComponent.h" -#include "../widgets/Slider.h" -#include "../widgets/TextControls.h" #include "../gui/CGuiHandler.h" #include "../render/CAnimation.h" -#include "../render/IImage.h" -#include "../render/IRenderHandler.h" #include "../render/Canvas.h" #include "../render/Graphics.h" #include "../render/IFont.h" -#include "../renderSDL/SDL_Extensions.h" +#include "../render/IImage.h" +#include "../render/IRenderHandler.h" +#include "../widgets/Buttons.h" +#include "../widgets/CComponent.h" +#include "../widgets/Images.h" +#include "../widgets/Slider.h" +#include "../widgets/TextControls.h" +#include "../windows/InfoWindows.h" const int BEFORE_COMPONENTS = 30; const int SIDE_MARGIN = 30; -namespace -{ - std::array, PlayerColor::PLAYER_LIMIT_I> dialogBorders; - std::array>, PlayerColor::PLAYER_LIMIT_I> piecesOfBox; -} +static std::array, PlayerColor::PLAYER_LIMIT_I> dialogBorders; +static std::array>, PlayerColor::PLAYER_LIMIT_I> piecesOfBox; void CMessage::init() { - for(int i=0; ipreload(); - for(int j=0; j < dialogBorders[i]->size(0); j++) + for(int j = 0; j < dialogBorders[i]->size(0); j++) { auto image = dialogBorders[i]->getImage(j, 0); //assume blue color initially @@ -63,42 +57,42 @@ void CMessage::dispose() item.reset(); } -std::vector CMessage::breakText( std::string text, size_t maxLineWidth, EFonts font ) +std::vector CMessage::breakText(std::string text, size_t maxLineWidth, EFonts font) { assert(maxLineWidth != 0); - if (maxLineWidth == 0) - return { text }; + if(maxLineWidth == 0) + return {text}; std::vector ret; - boost::algorithm::trim_right_if(text,boost::algorithm::is_any_of(std::string(" "))); + boost::algorithm::trim_right_if(text, boost::algorithm::is_any_of(std::string(" "))); // each iteration generates one output line - while (text.length()) + while(text.length()) { - ui32 lineWidth = 0; //in characters or given char metric - ui32 wordBreak = -1; //last position for line break (last space character) - ui32 currPos = 0; //current position in text - bool opened = false; //set to true when opening brace is found - std::string color = ""; //color found + ui32 lineWidth = 0; //in characters or given char metric + ui32 wordBreak = -1; //last position for line break (last space character) + ui32 currPos = 0; //current position in text + bool opened = false; //set to true when opening brace is found + std::string color = ""; //color found size_t symbolSize = 0; // width of character, in bytes size_t glyphWidth = 0; // width of printable glyph, pixels // loops till line is full or end of text reached - while(currPos < text.length() && text[currPos] != 0x0a && lineWidth < maxLineWidth) + while(currPos < text.length() && text[currPos] != 0x0a && lineWidth < maxLineWidth) { symbolSize = TextOperations::getUnicodeCharacterSize(text[currPos]); glyphWidth = graphics->fonts[font]->getGlyphWidth(text.data() + currPos); // candidate for line break - if (ui8(text[currPos]) <= ui8(' ')) + if(ui8(text[currPos]) <= ui8(' ')) wordBreak = currPos; /* We don't count braces in string length. */ - if (text[currPos] == '{') + if(text[currPos] == '{') { - opened=true; + opened = true; std::smatch match; std::regex expr("^\\{(.*?)\\|"); @@ -113,9 +107,9 @@ std::vector CMessage::breakText( std::string text, size_t maxLineWi } } } - else if (text[currPos]=='}') + else if(text[currPos] == '}') { - opened=false; + opened = false; color = ""; } else @@ -124,9 +118,9 @@ std::vector CMessage::breakText( std::string text, size_t maxLineWi } // long line, create line break - if (currPos < text.length() && (text[currPos] != 0x0a)) + if(currPos < text.length() && (text[currPos] != 0x0a)) { - if (wordBreak != ui32(-1)) + if(wordBreak != ui32(-1)) currPos = wordBreak; else currPos -= (ui32)symbolSize; @@ -137,7 +131,7 @@ std::vector CMessage::breakText( std::string text, size_t maxLineWi { ret.push_back(text.substr(0, currPos)); - if (opened) + if(opened) /* Close the brace for the current line. */ ret.back() += '}'; @@ -148,7 +142,7 @@ std::vector CMessage::breakText( std::string text, size_t maxLineWi ret.push_back(""); //add empty string, no extra actions needed } - if (text.length() != 0 && text[0] == 0x0a) + if(text.length() != 0 && text[0] == 0x0a) { /* Remove LF */ text.erase(0, 1); @@ -157,19 +151,19 @@ std::vector CMessage::breakText( std::string text, size_t maxLineWi { // trim only if line does not starts with LF // FIXME: necessary? All lines will be trimmed before returning anyway - boost::algorithm::trim_left_if(text,boost::algorithm::is_any_of(std::string(" "))); + boost::algorithm::trim_left_if(text, boost::algorithm::is_any_of(std::string(" "))); } - if (opened) + if(opened) { /* Add an opening brace for the next line. */ - if (text.length() != 0) + if(text.length() != 0) text.insert(0, "{" + color); } } /* Trim whitespaces of every line. */ - for (auto & elem : ret) + for(auto & elem : ret) boost::algorithm::trim(elem); return ret; @@ -196,11 +190,11 @@ int CMessage::guessHeight(const std::string & txt, int width, EFonts font) int CMessage::getEstimatedComponentHeight(int numComps) { - if (numComps > 8) //Bigger than 8 components - return invalid value + if(numComps > 8) //Bigger than 8 components - return invalid value return std::numeric_limits::max(); - else if (numComps > 2) + else if(numComps > 2) return 160; // 32px * 1 row + 20 to offset - else if (numComps) + else if(numComps) return 118; // 118 px to offset return 0; } @@ -208,17 +202,17 @@ int CMessage::getEstimatedComponentHeight(int numComps) void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor player) { constexpr std::array textAreaSizes = { - Point(300, 200), // if message is small, h3 will use 300px-wide text box with up to 200px height - Point(400, 200), // once text no longer fits into 300x200 box, h3 will start using 400px - wide boxes - Point(600, 200) // if 400px is not enough either, h3 will use largest, 600px-wide textbox, potentially with slider + Point(300, 200), // if message is small, h3 will use 300px-wide text box with up to 200px height + Point(400, 200), // once text no longer fits into 300x200 box, h3 will start using 400px - wide boxes + Point(600, 200) // if 400px is not enough either, h3 will use largest, 600px-wide textbox, potentially with slider }; assert(ret && ret->text); - for (auto const & area : textAreaSizes) + for(const auto & area : textAreaSizes) { ret->text->resize(area); - if (!ret->text->slider) + if(!ret->text->slider) break; // suitable size found, use it } @@ -229,27 +223,27 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play Point winSize(ret->text->pos.w, ret->text->pos.h); //start with text size - if (ret->components) + if(ret->components) winSize.y += 10 + ret->components->pos.h; //space to first component int bw = 0; - if (ret->buttons.size()) + if(ret->buttons.size()) { int bh = 0; // Compute total width of buttons - bw = 20*((int)ret->buttons.size()-1); // space between all buttons + bw = 20 * ((int)ret->buttons.size() - 1); // space between all buttons for(auto & elem : ret->buttons) //and add buttons width { - bw+=elem->pos.w; + bw += elem->pos.w; vstd::amax(bh, elem->pos.h); } - winSize.y += 20 + bh;//before button + button + winSize.y += 20 + bh; //before button + button } // Clip window size vstd::amax(winSize.y, 50); vstd::amax(winSize.x, 80); - if (ret->components) + if(ret->components) vstd::amax(winSize.x, ret->components->pos.w); vstd::amax(winSize.x, bw); @@ -261,19 +255,19 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play ret->backgroundTexture->pos = ret->pos; int curh = SIDE_MARGIN; - int xOffset = (ret->pos.w - ret->text->pos.w)/2; + int xOffset = (ret->pos.w - ret->text->pos.w) / 2; if(ret->buttons.empty() && !ret->components) //improvement for very small text only popups -> center text vertically { - if(ret->pos.h > ret->text->pos.h + 2*SIDE_MARGIN) - curh = (ret->pos.h - ret->text->pos.h)/2; + if(ret->pos.h > ret->text->pos.h + 2 * SIDE_MARGIN) + curh = (ret->pos.h - ret->text->pos.h) / 2; } ret->text->moveBy(Point(xOffset, curh)); curh += ret->text->pos.h; - if (ret->components) + if(ret->components) { curh += BEFORE_COMPONENTS; curh += ret->components->pos.h; @@ -282,7 +276,7 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play if(ret->buttons.size()) { // Position the buttons at the bottom of the window - bw = (ret->pos.w/2) - (bw/2); + bw = (ret->pos.w / 2) - (bw / 2); curh = ret->pos.h - SIDE_MARGIN - ret->buttons[0]->pos.h; for(auto & elem : ret->buttons) @@ -291,7 +285,7 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play bw += elem->pos.w + 20; } } - if (ret->components) + if(ret->components) ret->components->moveBy(Point(ret->pos.x, ret->pos.y)); } @@ -306,8 +300,9 @@ void CMessage::drawBorder(PlayerColor playerColor, Canvas & to, int w, int h, in // Horizontal borders int start_x = x + box[0]->width(); const int stop_x = x + w - box[1]->width(); - const int bottom_y = y+h-box[7]->height()+1; - while (start_x < stop_x) { + const int bottom_y = y + h - box[7]->height() + 1; + while(start_x < stop_x) + { // Top border to.draw(box[6], Point(start_x, y)); @@ -319,9 +314,10 @@ void CMessage::drawBorder(PlayerColor playerColor, Canvas & to, int w, int h, in // Vertical borders int start_y = y + box[0]->height(); - const int stop_y = y + h - box[2]->height()+1; - const int right_x = x+w-box[5]->width(); - while (start_y < stop_y) { + const int stop_y = y + h - box[2]->height() + 1; + const int right_x = x + w - box[5]->width(); + while(start_y < stop_y) + { // Left border to.draw(box[4], Point(x, start_y)); @@ -332,8 +328,8 @@ void CMessage::drawBorder(PlayerColor playerColor, Canvas & to, int w, int h, in } //corners - to.draw(box[0], Point(x,y)); - to.draw(box[1], Point(x+w-box[1]->width(), y)); - to.draw(box[2], Point(x, y+h-box[2]->height()+1)); - to.draw(box[3], Point(x+w-box[3]->width(), y+h-box[3]->height()+1)); + to.draw(box[0], Point(x, y)); + to.draw(box[1], Point(x + w - box[1]->width(), y)); + to.draw(box[2], Point(x, y + h - box[2]->height() + 1)); + to.draw(box[3], Point(x + w - box[3]->width(), y + h - box[3]->height() + 1)); } diff --git a/client/windows/CMessage.h b/client/windows/CMessage.h index 67b8d5b47..31c5d466e 100644 --- a/client/windows/CMessage.h +++ b/client/windows/CMessage.h @@ -21,7 +21,6 @@ VCMI_LIB_NAMESPACE_BEGIN class ColorRGBA; VCMI_LIB_NAMESPACE_END - /// Class which draws formatted text messages and generates chat windows class CMessage { diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index d9ab69fcd..7709eb7eb 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -11,19 +11,14 @@ #include "InfoWindows.h" #include "../CGameInfo.h" -#include "../PlayerLocalState.h" #include "../CPlayerInterface.h" -#include "../CMusicHandler.h" +#include "../PlayerLocalState.h" #include "../adventureMap/AdventureMapInterface.h" -#include "../battle/BattleInterface.h" -#include "../battle/BattleInterfaceClasses.h" #include "../gui/CGuiHandler.h" #include "../gui/CursorHandler.h" #include "../gui/Shortcut.h" #include "../gui/WindowHandler.h" -#include "../render/Canvas.h" -#include "../renderSDL/SDL_Extensions.h" #include "../widgets/Buttons.h" #include "../widgets/CComponent.h" #include "../widgets/Images.h" @@ -35,35 +30,32 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/CondSh.h" -#include "../../lib/CGeneralTextHandler.h" //for Unicode related stuff +#include "../../lib/gameState/InfoAboutArmy.h" #include "../../lib/mapObjects/CGCreature.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/MiscObjects.h" -#include "../../lib/gameState/InfoAboutArmy.h" -CSelWindow::CSelWindow(const std::string &Text, PlayerColor player, int charperline, const std::vector> & comps, const std::vector > > &Buttons, QueryID askID) +CSelWindow::CSelWindow( const std::string & Text, PlayerColor player, int charperline, const std::vector> & comps, const std::vector>> & Buttons, QueryID askID) { - OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE); backgroundTexture = std::make_shared(ImagePath::builtin("DiBoxBck"), pos); ID = askID; - for (int i = 0; i < Buttons.size(); i++) + for(int i = 0; i < Buttons.size(); i++) { buttons.push_back(std::make_shared(Point(0, 0), Buttons[i].first, CButton::tooltip(), Buttons[i].second)); - if (!i && askID.getNum() >= 0) + if(!i && askID.getNum() >= 0) buttons.back()->addCallback(std::bind(&CSelWindow::madeChoice, this)); buttons[i]->addCallback(std::bind(&CInfoWindow::close, this)); //each button will close the window apart from call-defined actions } text = std::make_shared(Text, Rect(0, 0, 250, 100), 0, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE); - if (buttons.size() > 1 && askID.getNum() >= 0) //cancel button functionality + if(buttons.size() > 1 && askID.getNum() >= 0) //cancel button functionality { - buttons.back()->addCallback([askID]() { - LOCPLINT->cb.get()->selectionMade(0, askID); - }); + buttons.back()->addCallback([askID](){LOCPLINT->cb.get()->selectionMade(0, askID);}); //buttons.back()->addCallback(std::bind(&CCallback::selectionMade, LOCPLINT->cb.get(), 0, askID)); } @@ -76,8 +68,8 @@ CSelWindow::CSelWindow(const std::string &Text, PlayerColor player, int charperl buttons.back()->assignedKey = EShortcut::GLOBAL_CANCEL; } - if (!comps.empty()) - components = std::make_shared(comps, Rect(0,0,600,300)); + if(!comps.empty()) + components = std::make_shared(comps, Rect(0, 0, 600, 300)); CMessage::drawIWindow(this, Text, player); } @@ -87,10 +79,10 @@ void CSelWindow::madeChoice() if(ID.getNum() < 0) return; int ret = -1; - if (components) + if(components) ret = components->selectedIndex(); - LOCPLINT->cb->selectionMade(ret+1,ID); + LOCPLINT->cb->selectionMade(ret + 1, ID); } void CSelWindow::madeChoiceAndClose() @@ -101,14 +93,14 @@ void CSelWindow::madeChoiceAndClose() CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo & comps, const TButtonsInfo & Buttons) { - OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE); backgroundTexture = std::make_shared(ImagePath::builtin("DiBoxBck"), pos); ID = QueryID(-1); for(auto & Button : Buttons) { - std::shared_ptr button = std::make_shared(Point(0,0), Button.first, CButton::tooltip(), std::bind(&CInfoWindow::close, this)); + std::shared_ptr button = std::make_shared(Point(0, 0), Button.first, CButton::tooltip(), std::bind(&CInfoWindow::close, this)); button->setBorderColor(Colors::METALLIC_GOLD); button->addCallback(Button.second); //each button will close the window apart from call-defined actions buttons.push_back(button); @@ -131,10 +123,10 @@ CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo buttons.back()->assignedKey = EShortcut::GLOBAL_CANCEL; } - if (!comps.empty()) - components = std::make_shared(comps, Rect(0,0,600,300)); + if(!comps.empty()) + components = std::make_shared(comps, Rect(0, 0, 600, 300)); - CMessage::drawIWindow(this,Text,player); + CMessage::drawIWindow(this, Text, player); } CInfoWindow::CInfoWindow() @@ -158,28 +150,28 @@ void CInfoWindow::showAll(Canvas & to) CInfoWindow::~CInfoWindow() = default; -void CInfoWindow::showInfoDialog(const std::string &text, const TCompsInfo & components, PlayerColor player) +void CInfoWindow::showInfoDialog(const std::string & text, const TCompsInfo & components, PlayerColor player) { GH.windows().pushWindow(CInfoWindow::create(text, player, components)); } -void CInfoWindow::showYesNoDialog(const std::string & text, const TCompsInfo & components, const CFunctionList &onYes, const CFunctionList &onNo, PlayerColor player) +void CInfoWindow::showYesNoDialog(const std::string & text, const TCompsInfo & components, const CFunctionList & onYes, const CFunctionList & onNo, PlayerColor player) { assert(!LOCPLINT || LOCPLINT->showingDialog->get()); - std::vector > > pom; - pom.push_back( { AnimationPath::builtin("IOKAY.DEF"), 0 }); - pom.push_back( { AnimationPath::builtin("ICANCEL.DEF"), 0 }); - std::shared_ptr temp = std::make_shared(text, player, components, pom); + std::vector>> pom; + pom.push_back({AnimationPath::builtin("IOKAY.DEF"), 0}); + pom.push_back({AnimationPath::builtin("ICANCEL.DEF"), 0}); + std::shared_ptr temp = std::make_shared(text, player, components, pom); - temp->buttons[0]->addCallback( onYes ); - temp->buttons[1]->addCallback( onNo ); + temp->buttons[0]->addCallback(onYes); + temp->buttons[1]->addCallback(onNo); GH.windows().pushWindow(temp); } -std::shared_ptr CInfoWindow::create(const std::string &text, PlayerColor playerID, const TCompsInfo & components) +std::shared_ptr CInfoWindow::create(const std::string & text, PlayerColor playerID, const TCompsInfo & components) { - std::vector > > pom; + std::vector>> pom; pom.push_back({AnimationPath::builtin("IOKAY.DEF"), 0}); return std::make_shared(text, playerID, components, pom); } @@ -199,10 +191,10 @@ void CRClickPopup::close() WindowBase::close(); } -void CRClickPopup::createAndPush(const std::string &txt, const CInfoWindow::TCompsInfo &comps) +void CRClickPopup::createAndPush(const std::string & txt, const CInfoWindow::TCompsInfo & comps) { PlayerColor player = LOCPLINT ? LOCPLINT->playerID : PlayerColor(1); //if no player, then use blue - if(settings["session"]["spectate"].Bool())//TODO: there must be better way to implement this + if(settings["session"]["spectate"].Bool()) //TODO: there must be better way to implement this player = PlayerColor(1); auto temp = std::make_shared(txt, player, comps); @@ -233,7 +225,7 @@ void CRClickPopup::createAndPush(const CGObjectInstance * obj, const Point & p, else { std::vector components; - if (settings["general"]["enableUiEnhancements"].Bool()) + if(settings["general"]["enableUiEnhancements"].Bool()) { if(LOCPLINT->localState->getCurrentHero()) components = obj->getPopupComponents(LOCPLINT->localState->getCurrentHero()); @@ -242,7 +234,7 @@ void CRClickPopup::createAndPush(const CGObjectInstance * obj, const Point & p, } std::vector> guiComponents; - for (auto & component : components) + for(auto & component : components) guiComponents.push_back(std::make_shared(component)); if(LOCPLINT->localState->getCurrentHero()) @@ -282,7 +274,7 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town) InfoAboutTown iah; LOCPLINT->cb->getTownInfo(town, iah, LOCPLINT->localState->getCurrentTown()); //todo: should this be nearest hero? - OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE); tooltip = std::make_shared(Point(9, 10), iah); } @@ -290,9 +282,9 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero) : CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("HEROQVBK"), toScreen(position)) { InfoAboutHero iah; - LOCPLINT->cb->getHeroInfo(hero, iah, LOCPLINT->localState->getCurrentHero());//todo: should this be nearest hero? + LOCPLINT->cb->getHeroInfo(hero, iah, LOCPLINT->localState->getCurrentHero()); //todo: should this be nearest hero? - OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE); tooltip = std::make_shared(Point(9, 10), iah); } @@ -302,18 +294,19 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr) InfoAboutTown iah; LOCPLINT->cb->getTownInfo(garr, iah); - OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE); tooltip = std::make_shared(Point(9, 10), iah); } CInfoBoxPopup::CInfoBoxPopup(Point position, const CGCreature * creature) - : CWindowObject(RCLICK_POPUP | BORDERED, ImagePath::builtin("DIBOXBCK"), toScreen(position)) + : CWindowObject(RCLICK_POPUP | BORDERED, ImagePath::builtin("DIBOXBCK"), toScreen(position)) { - OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); + OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE); tooltip = std::make_shared(Point(9, 10), creature); } -std::shared_ptr CRClickPopup::createCustomInfoWindow(Point position, const CGObjectInstance * specific) //specific=0 => draws info about selected town/hero +std::shared_ptr +CRClickPopup::createCustomInfoWindow(Point position, const CGObjectInstance * specific) //specific=0 => draws info about selected town/hero { if(nullptr == specific) specific = LOCPLINT->localState->getCurrentArmy(); @@ -326,16 +319,16 @@ std::shared_ptr CRClickPopup::createCustomInfoWindow(Point position, switch(specific->ID) { - case Obj::HERO: - return std::make_shared(position, dynamic_cast(specific)); - case Obj::TOWN: - return std::make_shared(position, dynamic_cast(specific)); - case Obj::MONSTER: - return std::make_shared(position, dynamic_cast(specific)); - case Obj::GARRISON: - case Obj::GARRISON2: - return std::make_shared(position, dynamic_cast(specific)); - default: - return std::shared_ptr(); + case Obj::HERO: + return std::make_shared(position, dynamic_cast(specific)); + case Obj::TOWN: + return std::make_shared(position, dynamic_cast(specific)); + case Obj::MONSTER: + return std::make_shared(position, dynamic_cast(specific)); + case Obj::GARRISON: + case Obj::GARRISON2: + return std::make_shared(position, dynamic_cast(specific)); + default: + return std::shared_ptr(); } } diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h index 67cd75b49..da1f7a154 100644 --- a/client/windows/InfoWindows.h +++ b/client/windows/InfoWindows.h @@ -20,21 +20,14 @@ class CGTownInstance; class CGHeroInstance; class CGGarrison; class CGCreature; -class Rect; VCMI_LIB_NAMESPACE_END -struct SDL_Surface; -class CAnimImage; -class CLabel; -class CAnimation; class CComponent; class CComponentBox; class CSelectableComponent; class CTextBox; class CButton; -class CSlider; -class CArmyTooltip; class CFilledTexture; /// text + comp. + ok button @@ -59,8 +52,8 @@ public: ~CInfoWindow(); //use only before the game starts! (showYesNoDialog in LOCPLINT must be used then) - static void showInfoDialog( const std::string & text, const TCompsInfo & components, PlayerColor player = PlayerColor(1)); - static void showYesNoDialog( const std::string & text, const TCompsInfo & components, const CFunctionList & onYes, const CFunctionList & onNo, PlayerColor player = PlayerColor(1)); + static void showInfoDialog(const std::string & text, const TCompsInfo & components, PlayerColor player = PlayerColor(1)); + static void showYesNoDialog(const std::string & text, const TCompsInfo & components, const CFunctionList & onYes, const CFunctionList & onNo, PlayerColor player = PlayerColor(1)); static std::shared_ptr create(const std::string & text, PlayerColor playerID = PlayerColor(1), const TCompsInfo & components = TCompsInfo()); /// create text from title and description: {title}\n\n description @@ -75,7 +68,7 @@ public: bool isPopupWindow() const override; static std::shared_ptr createCustomInfoWindow(Point position, const CGObjectInstance * specific); - static void createAndPush(const std::string & txt, const CInfoWindow::TCompsInfo &comps = CInfoWindow::TCompsInfo()); + static void createAndPush(const std::string & txt, const CInfoWindow::TCompsInfo & comps = CInfoWindow::TCompsInfo()); static void createAndPush(const std::string & txt, std::shared_ptr component); static void createAndPush(const CGObjectInstance * obj, const Point & p, ETextAlignment alignment = ETextAlignment::BOTTOMRIGHT); }; @@ -84,6 +77,7 @@ public: class CRClickPopupInt : public CRClickPopup { std::shared_ptr inner; + public: CRClickPopupInt(std::shared_ptr our); virtual ~CRClickPopupInt(); @@ -94,6 +88,7 @@ class CInfoBoxPopup : public CWindowObject { std::shared_ptr tooltip; Point toScreen(Point pos); + public: CInfoBoxPopup(Point position, const CGTownInstance * town); CInfoBoxPopup(Point position, const CGHeroInstance * hero); @@ -108,6 +103,4 @@ public: void madeChoice(); //looks for selected component and calls callback void madeChoiceAndClose(); CSelWindow(const std::string & text, PlayerColor player, int charperline, const std::vector> & comps, const std::vector > > &Buttons, QueryID askID); - - //notification - this class inherits important destructor from CInfoWindow }; From ec159f2fdd1ae59494fd30fc6d510bbf973ef673 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Feb 2024 22:04:02 +0200 Subject: [PATCH 165/250] Fixed bad line breaks in starting faction description popup --- client/widgets/CComponent.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index b3f725746..cff5ec9db 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -80,13 +80,14 @@ void CComponent::init(ComponentType Type, ComponentSubType Subtype, std::optiona pos.h += 4; //distance between text and image - auto max = 80; + // WARNING: too low values will lead to bad line-breaks in CPlayerOptionTooltipBox - check right-click on starting town in pregame + int max = 80; if (size < large) max = 72; if (size < medium) - max = 40; + max = 60; if (size < small) - max = 30; + max = 55; if(Type == ComponentType::RESOURCE && !ValText.empty()) max = 80; From 93b3cb3af598939f118f7245a4eb0c28c2601372 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Feb 2024 22:04:30 +0200 Subject: [PATCH 166/250] Better size & positioning of infoboxes --- client/widgets/TextControls.cpp | 3 ++ client/windows/CMessage.cpp | 80 +++++++++++++++++---------------- client/windows/InfoWindows.cpp | 2 +- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index 32b477818..355e7a744 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -374,6 +374,9 @@ void CTextBox::trimToFit() if (slider) return; + if(label->alignment == ETextAlignment::CENTER) + moveBy((pos.dimensions() - label->textSize) / 2); + pos.w = label->textSize.x; pos.h = label->textSize.y; label->pos.w = label->textSize.x; diff --git a/client/windows/CMessage.cpp b/client/windows/CMessage.cpp index ce78a2c70..5fc1ac083 100644 --- a/client/windows/CMessage.cpp +++ b/client/windows/CMessage.cpp @@ -27,8 +27,12 @@ #include "../widgets/TextControls.h" #include "../windows/InfoWindows.h" -const int BEFORE_COMPONENTS = 30; -const int SIDE_MARGIN = 30; +constexpr int BEFORE_COMPONENTS = 30; +constexpr int SIDE_MARGIN = 11; +constexpr int TOP_MARGIN = 20; +constexpr int BOTTOM_MARGIN = 16; +constexpr int INTERVAL_BETWEEN_BUTTONS = 18; +constexpr int INTERVAL_BETWEEN_TEXT_AND_BUTTONS = 24; static std::array, PlayerColor::PLAYER_LIMIT_I> dialogBorders; static std::array>, PlayerColor::PLAYER_LIMIT_I> piecesOfBox; @@ -201,10 +205,17 @@ int CMessage::getEstimatedComponentHeight(int numComps) void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor player) { + // possible sizes of text boxes section of window + // game should pick smallest one that can fit text without slider + // or, if not possible - pick last one and use slider constexpr std::array textAreaSizes = { - Point(300, 200), // if message is small, h3 will use 300px-wide text box with up to 200px height - Point(400, 200), // once text no longer fits into 300x200 box, h3 will start using 400px - wide boxes - Point(600, 200) // if 400px is not enough either, h3 will use largest, 600px-wide textbox, potentially with slider +// Point(206, 72), // NOTE: this one should only be used for single-line texts + Point(270, 72), + Point(270, 136), + Point(270, 200), + Point(400, 136), + Point(400, 200), + Point(590, 200) }; assert(ret && ret->text); @@ -219,74 +230,67 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play if(ret->text->slider) ret->text->slider->addUsedEvents(CIntObject::WHEEL | CIntObject::KEYBOARD); - ret->text->trimToFit(); - Point winSize(ret->text->pos.w, ret->text->pos.h); //start with text size if(ret->components) winSize.y += 10 + ret->components->pos.h; //space to first component - int bw = 0; + int buttonsWidth = 0; if(ret->buttons.size()) { int bh = 0; // Compute total width of buttons - bw = 20 * ((int)ret->buttons.size() - 1); // space between all buttons + buttonsWidth = INTERVAL_BETWEEN_BUTTONS * (ret->buttons.size() - 1); // space between all buttons for(auto & elem : ret->buttons) //and add buttons width { - bw += elem->pos.w; + buttonsWidth += elem->pos.w; vstd::amax(bh, elem->pos.h); } - winSize.y += 20 + bh; //before button + button + winSize.y += INTERVAL_BETWEEN_TEXT_AND_BUTTONS + bh; //before button + button } // Clip window size - vstd::amax(winSize.y, 50); - vstd::amax(winSize.x, 80); if(ret->components) vstd::amax(winSize.x, ret->components->pos.w); - vstd::amax(winSize.x, bw); + vstd::amax(winSize.x, buttonsWidth); - vstd::amin(winSize.x, GH.screenDimensions().x - 150); + vstd::amin(winSize.x, GH.screenDimensions().x); + vstd::amin(winSize.y, GH.screenDimensions().y); - ret->pos.h = winSize.y + 2 * SIDE_MARGIN; + ret->pos.h = winSize.y + TOP_MARGIN + BOTTOM_MARGIN; ret->pos.w = winSize.x + 2 * SIDE_MARGIN; ret->center(); - ret->backgroundTexture->pos = ret->pos; - int curh = SIDE_MARGIN; - int xOffset = (ret->pos.w - ret->text->pos.w) / 2; + Point marginTopLeft(SIDE_MARGIN, TOP_MARGIN); + ret->text->moveBy(marginTopLeft); + ret->text->trimToFit(); - if(ret->buttons.empty() && !ret->components) //improvement for very small text only popups -> center text vertically + if(ret->buttons.empty() && !ret->components) { - if(ret->pos.h > ret->text->pos.h + 2 * SIDE_MARGIN) - curh = (ret->pos.h - ret->text->pos.h) / 2; + //improvement for text only right-click popups -> center text + Point distance = ret->pos.topLeft() - ret->text->pos.topLeft() + marginTopLeft; + ret->text->moveBy(distance); + + ret->pos.h = ret->text->pos.h + TOP_MARGIN + BOTTOM_MARGIN; + ret->pos.w = ret->text->pos.w + 2 * SIDE_MARGIN; } - ret->text->moveBy(Point(xOffset, curh)); - - curh += ret->text->pos.h; - - if(ret->components) + if(!ret->buttons.empty()) { - curh += BEFORE_COMPONENTS; - curh += ret->components->pos.h; - } - - if(ret->buttons.size()) - { - // Position the buttons at the bottom of the window - bw = (ret->pos.w / 2) - (bw / 2); - curh = ret->pos.h - SIDE_MARGIN - ret->buttons[0]->pos.h; + int buttonPosX = ret->pos.w / 2 - buttonsWidth / 2; + int buttonPosY = ret->pos.h - BOTTOM_MARGIN - ret->buttons[0]->pos.h; for(auto & elem : ret->buttons) { - elem->moveBy(Point(bw, curh)); - bw += elem->pos.w + 20; + elem->moveBy(Point(buttonPosX, buttonPosY)); + buttonPosX += elem->pos.w + INTERVAL_BETWEEN_BUTTONS; } } + if(ret->components) ret->components->moveBy(Point(ret->pos.x, ret->pos.y)); + + ret->backgroundTexture->pos = ret->pos; } void CMessage::drawBorder(PlayerColor playerColor, Canvas & to, int w, int h, int x, int y) diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index 7709eb7eb..19c938bc0 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -145,7 +145,7 @@ void CInfoWindow::close() void CInfoWindow::showAll(Canvas & to) { CIntObject::showAll(to); - CMessage::drawBorder(LOCPLINT ? LOCPLINT->playerID : PlayerColor(1), to, pos.w, pos.h, pos.x, pos.y); + CMessage::drawBorder(LOCPLINT ? LOCPLINT->playerID : PlayerColor(1), to, pos.w+28, pos.h+29, pos.x-14, pos.y-15); } CInfoWindow::~CInfoWindow() = default; From 99cb1df91d55f2c155552d9e2f9f6ea0d7634a75 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 26 Feb 2024 23:26:06 +0200 Subject: [PATCH 167/250] Fixes for positioning on popups with components --- client/lobby/OptionsTab.cpp | 2 +- client/widgets/CComponent.cpp | 1 + client/widgets/CComponent.h | 2 +- client/widgets/TextControls.cpp | 3 -- client/windows/CMessage.cpp | 94 +++++++++++++++++++-------------- client/windows/InfoWindows.cpp | 4 +- 6 files changed, 60 insertions(+), 46 deletions(-) diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 99f6851f7..2f1dc5cbb 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -374,7 +374,7 @@ void OptionsTab::CPlayerOptionTooltipBox::genTownWindow() if(!elem.empty()) components.push_back(std::make_shared(ComponentType::CREATURE, elem.front(), std::nullopt, CComponent::tiny)); } - boxAssociatedCreatures = std::make_shared(components, Rect(10, 140, pos.w - 20, 140)); + boxAssociatedCreatures = std::make_shared(components, Rect(10, 140, pos.w - 20, 140), 20, 10, 22, 4); } void OptionsTab::CPlayerOptionTooltipBox::genHeroWindow() diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index cff5ec9db..d8e7a7d97 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -427,6 +427,7 @@ void CComponentBox::placeComponents(bool selectable) for(auto & comp : components) { addChild(comp.get()); + comp->recActions = defActions; //FIXME: for some reason, received component might have recActions set to 0 comp->moveTo(Point(pos.x, pos.y)); } diff --git a/client/widgets/CComponent.h b/client/widgets/CComponent.h index f41ea3264..f4d360460 100644 --- a/client/widgets/CComponent.h +++ b/client/widgets/CComponent.h @@ -92,7 +92,7 @@ class CComponentBox : public CIntObject std::shared_ptr selected; std::function onSelect; - static constexpr int defaultBetweenImagesMin = 20; + static constexpr int defaultBetweenImagesMin = 42; static constexpr int defaultBetweenSubtitlesMin = 10; static constexpr int defaultBetweenRows = 22; static constexpr int defaultComponentsInRow = 4; diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index 355e7a744..32b477818 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -374,9 +374,6 @@ void CTextBox::trimToFit() if (slider) return; - if(label->alignment == ETextAlignment::CENTER) - moveBy((pos.dimensions() - label->textSize) / 2); - pos.w = label->textSize.x; pos.h = label->textSize.y; label->pos.w = label->textSize.x; diff --git a/client/windows/CMessage.cpp b/client/windows/CMessage.cpp index 5fc1ac083..a7ade93b5 100644 --- a/client/windows/CMessage.cpp +++ b/client/windows/CMessage.cpp @@ -27,6 +27,7 @@ #include "../widgets/TextControls.h" #include "../windows/InfoWindows.h" +constexpr int RIGHT_CLICK_POPUP_MIN_SIZE = 100; constexpr int BEFORE_COMPONENTS = 30; constexpr int SIDE_MARGIN = 11; constexpr int TOP_MARGIN = 20; @@ -220,6 +221,8 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play assert(ret && ret->text); + // STEP 1: DETERMINE SIZE OF ALL ELEMENTS + for(const auto & area : textAreaSizes) { ret->text->resize(area); @@ -227,70 +230,83 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play break; // suitable size found, use it } +// int textWidth = ret->text->pos.w; + int textHeight = ret->text->pos.h; + if(ret->text->slider) ret->text->slider->addUsedEvents(CIntObject::WHEEL | CIntObject::KEYBOARD); - Point winSize(ret->text->pos.w, ret->text->pos.h); //start with text size - - if(ret->components) - winSize.y += 10 + ret->components->pos.h; //space to first component - int buttonsWidth = 0; - if(ret->buttons.size()) + int buttonsHeight = 0; + if(!ret->buttons.empty()) { - int bh = 0; // Compute total width of buttons buttonsWidth = INTERVAL_BETWEEN_BUTTONS * (ret->buttons.size() - 1); // space between all buttons for(auto & elem : ret->buttons) //and add buttons width { buttonsWidth += elem->pos.w; - vstd::amax(bh, elem->pos.h); + vstd::amax(buttonsHeight, elem->pos.h); } - winSize.y += INTERVAL_BETWEEN_TEXT_AND_BUTTONS + bh; //before button + button } - // Clip window size - if(ret->components) - vstd::amax(winSize.x, ret->components->pos.w); - vstd::amax(winSize.x, buttonsWidth); - - vstd::amin(winSize.x, GH.screenDimensions().x); - vstd::amin(winSize.y, GH.screenDimensions().y); - - ret->pos.h = winSize.y + TOP_MARGIN + BOTTOM_MARGIN; - ret->pos.w = winSize.x + 2 * SIDE_MARGIN; - ret->center(); - - Point marginTopLeft(SIDE_MARGIN, TOP_MARGIN); - ret->text->moveBy(marginTopLeft); - ret->text->trimToFit(); + // STEP 2: COMPUTE WINDOW SIZE if(ret->buttons.empty() && !ret->components) { - //improvement for text only right-click popups -> center text - Point distance = ret->pos.topLeft() - ret->text->pos.topLeft() + marginTopLeft; - ret->text->moveBy(distance); + // use more compact form for right-click popup with no buttons / components - ret->pos.h = ret->text->pos.h + TOP_MARGIN + BOTTOM_MARGIN; - ret->pos.w = ret->text->pos.w + 2 * SIDE_MARGIN; + ret->pos.w = std::max(RIGHT_CLICK_POPUP_MIN_SIZE, ret->text->label->textSize.x + 2 * SIDE_MARGIN); + ret->pos.h = std::max(RIGHT_CLICK_POPUP_MIN_SIZE, ret->text->label->textSize.y + TOP_MARGIN + BOTTOM_MARGIN); + } + else + { + int windowContentWidth = ret->text->pos.w; + int windowContentHeight = ret->text->pos.h; + if(ret->components) + { + vstd::amax(windowContentWidth, ret->components->pos.w); + windowContentHeight += INTERVAL_BETWEEN_TEXT_AND_BUTTONS + ret->components->pos.h; + } + if(!ret->buttons.empty()) + { + vstd::amax(windowContentWidth, buttonsWidth); + windowContentHeight += INTERVAL_BETWEEN_TEXT_AND_BUTTONS + buttonsHeight; + } + + ret->pos.w = windowContentWidth + 2 * SIDE_MARGIN; + ret->pos.h = windowContentHeight + TOP_MARGIN + BOTTOM_MARGIN; } - if(!ret->buttons.empty()) - { - int buttonPosX = ret->pos.w / 2 - buttonsWidth / 2; - int buttonPosY = ret->pos.h - BOTTOM_MARGIN - ret->buttons[0]->pos.h; + // STEP 3: MOVE ALL ELEMENTS IN PLACE - for(auto & elem : ret->buttons) + if(ret->buttons.empty() && !ret->components) + { + ret->text->trimToFit(); + ret->text->center(ret->pos.center()); + } + else + { + if(ret->components) + ret->components->moveBy(Point((ret->pos.w - ret->components->pos.w) / 2, TOP_MARGIN + ret->text->pos.h + INTERVAL_BETWEEN_TEXT_AND_BUTTONS)); + + ret->text->trimToFit(); + ret->text->moveBy(Point((ret->pos.w - ret->text->pos.w) / 2, TOP_MARGIN + (textHeight - ret->text->pos.h) / 2 )); + + if(!ret->buttons.empty()) { - elem->moveBy(Point(buttonPosX, buttonPosY)); - buttonPosX += elem->pos.w + INTERVAL_BETWEEN_BUTTONS; + int buttonPosX = ret->pos.w / 2 - buttonsWidth / 2; + int buttonPosY = ret->pos.h - BOTTOM_MARGIN - ret->buttons[0]->pos.h; + + for(auto & elem : ret->buttons) + { + elem->moveBy(Point(buttonPosX, buttonPosY)); + buttonPosX += elem->pos.w + INTERVAL_BETWEEN_BUTTONS; + } } } - if(ret->components) - ret->components->moveBy(Point(ret->pos.x, ret->pos.y)); - ret->backgroundTexture->pos = ret->pos; + ret->center(); } void CMessage::drawBorder(PlayerColor playerColor, Canvas & to, int w, int h, int x, int y) diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index 19c938bc0..97a3ffa50 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -69,7 +69,7 @@ CSelWindow::CSelWindow( const std::string & Text, PlayerColor player, int charpe } if(!comps.empty()) - components = std::make_shared(comps, Rect(0, 0, 600, 300)); + components = std::make_shared(comps, Rect(0,0,0,0)); CMessage::drawIWindow(this, Text, player); } @@ -124,7 +124,7 @@ CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo } if(!comps.empty()) - components = std::make_shared(comps, Rect(0, 0, 600, 300)); + components = std::make_shared(comps, Rect(0,0,0,0)); CMessage::drawIWindow(this, Text, player); } From 658747d34253a6ceabd9dd2a50227b2660efe4bc Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 27 Feb 2024 13:47:17 +0200 Subject: [PATCH 168/250] Code cleanup --- client/windows/CMessage.cpp | 26 ++++++++++++-------------- client/windows/InfoWindows.cpp | 22 +++++++++++----------- client/windows/InfoWindows.h | 10 +++++----- 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/client/windows/CMessage.cpp b/client/windows/CMessage.cpp index a7ade93b5..fa575c7e6 100644 --- a/client/windows/CMessage.cpp +++ b/client/windows/CMessage.cpp @@ -28,7 +28,6 @@ #include "../windows/InfoWindows.h" constexpr int RIGHT_CLICK_POPUP_MIN_SIZE = 100; -constexpr int BEFORE_COMPONENTS = 30; constexpr int SIDE_MARGIN = 11; constexpr int TOP_MARGIN = 20; constexpr int BOTTOM_MARGIN = 16; @@ -79,7 +78,7 @@ std::vector CMessage::breakText(std::string text, size_t maxLineWid ui32 wordBreak = -1; //last position for line break (last space character) ui32 currPos = 0; //current position in text bool opened = false; //set to true when opening brace is found - std::string color = ""; //color found + std::string color; //color found size_t symbolSize = 0; // width of character, in bytes size_t glyphWidth = 0; // width of printable glyph, pixels @@ -118,8 +117,8 @@ std::vector CMessage::breakText(std::string text, size_t maxLineWid color = ""; } else - lineWidth += (ui32)glyphWidth; - currPos += (ui32)symbolSize; + lineWidth += glyphWidth; + currPos += symbolSize; } // long line, create line break @@ -128,7 +127,7 @@ std::vector CMessage::breakText(std::string text, size_t maxLineWid if(wordBreak != ui32(-1)) currPos = wordBreak; else - currPos -= (ui32)symbolSize; + currPos -= symbolSize; } //non-blank line @@ -188,18 +187,18 @@ std::string CMessage::guessHeader(const std::string & msg) int CMessage::guessHeight(const std::string & txt, int width, EFonts font) { const auto f = graphics->fonts[font]; - auto lines = CMessage::breakText(txt, width, font); - int lineHeight = static_cast(f->getLineHeight()); - return lineHeight * (int)lines.size(); + const auto lines = CMessage::breakText(txt, width, font); + size_t lineHeight = f->getLineHeight(); + return lineHeight * lines.size(); } int CMessage::getEstimatedComponentHeight(int numComps) { if(numComps > 8) //Bigger than 8 components - return invalid value return std::numeric_limits::max(); - else if(numComps > 2) + if(numComps > 2) return 160; // 32px * 1 row + 20 to offset - else if(numComps) + if(numComps > 0) return 118; // 118 px to offset return 0; } @@ -210,7 +209,7 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play // game should pick smallest one that can fit text without slider // or, if not possible - pick last one and use slider constexpr std::array textAreaSizes = { -// Point(206, 72), // NOTE: this one should only be used for single-line texts + // FIXME: this size should only be used for single-line texts: Point(206, 72), Point(270, 72), Point(270, 136), Point(270, 200), @@ -230,7 +229,6 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play break; // suitable size found, use it } -// int textWidth = ret->text->pos.w; int textHeight = ret->text->pos.h; if(ret->text->slider) @@ -242,7 +240,7 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play { // Compute total width of buttons buttonsWidth = INTERVAL_BETWEEN_BUTTONS * (ret->buttons.size() - 1); // space between all buttons - for(auto & elem : ret->buttons) //and add buttons width + for(const auto & elem : ret->buttons) //and add buttons width { buttonsWidth += elem->pos.w; vstd::amax(buttonsHeight, elem->pos.h); @@ -297,7 +295,7 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play int buttonPosX = ret->pos.w / 2 - buttonsWidth / 2; int buttonPosY = ret->pos.h - BOTTOM_MARGIN - ret->buttons[0]->pos.h; - for(auto & elem : ret->buttons) + for(const auto & elem : ret->buttons) { elem->moveBy(Point(buttonPosX, buttonPosY)); buttonPosX += elem->pos.w + INTERVAL_BETWEEN_BUTTONS; diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index 97a3ffa50..59e95ea21 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -55,7 +55,7 @@ CSelWindow::CSelWindow( const std::string & Text, PlayerColor player, int charpe if(buttons.size() > 1 && askID.getNum() >= 0) //cancel button functionality { - buttons.back()->addCallback([askID](){LOCPLINT->cb.get()->selectionMade(0, askID);}); + buttons.back()->addCallback([askID](){LOCPLINT->cb->selectionMade(0, askID);}); //buttons.back()->addCallback(std::bind(&CCallback::selectionMade, LOCPLINT->cb.get(), 0, askID)); } @@ -91,16 +91,16 @@ void CSelWindow::madeChoiceAndClose() close(); } -CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo & comps, const TButtonsInfo & Buttons) +CInfoWindow::CInfoWindow(const std::string & Text, PlayerColor player, const TCompsInfo & comps, const TButtonsInfo & Buttons) { OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE); backgroundTexture = std::make_shared(ImagePath::builtin("DiBoxBck"), pos); ID = QueryID(-1); - for(auto & Button : Buttons) + for(const auto & Button : Buttons) { - std::shared_ptr button = std::make_shared(Point(0, 0), Button.first, CButton::tooltip(), std::bind(&CInfoWindow::close, this)); + auto button = std::make_shared(Point(0, 0), Button.first, CButton::tooltip(), std::bind(&CInfoWindow::close, this)); button->setBorderColor(Colors::METALLIC_GOLD); button->addCallback(Button.second); //each button will close the window apart from call-defined actions buttons.push_back(button); @@ -159,9 +159,9 @@ void CInfoWindow::showYesNoDialog(const std::string & text, const TCompsInfo & c { assert(!LOCPLINT || LOCPLINT->showingDialog->get()); std::vector>> pom; - pom.push_back({AnimationPath::builtin("IOKAY.DEF"), 0}); - pom.push_back({AnimationPath::builtin("ICANCEL.DEF"), 0}); - std::shared_ptr temp = std::make_shared(text, player, components, pom); + pom.emplace_back(AnimationPath::builtin("IOKAY.DEF"), nullptr); + pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr); + auto temp = std::make_shared(text, player, components, pom); temp->buttons[0]->addCallback(onYes); temp->buttons[1]->addCallback(onNo); @@ -172,11 +172,11 @@ void CInfoWindow::showYesNoDialog(const std::string & text, const TCompsInfo & c std::shared_ptr CInfoWindow::create(const std::string & text, PlayerColor playerID, const TCompsInfo & components) { std::vector>> pom; - pom.push_back({AnimationPath::builtin("IOKAY.DEF"), 0}); + pom.emplace_back(AnimationPath::builtin("IOKAY.DEF"), nullptr); return std::make_shared(text, playerID, components, pom); } -std::string CInfoWindow::genText(std::string title, std::string description) +std::string CInfoWindow::genText(const std::string & title, const std::string & description) { return std::string("{") + title + "}" + "\n\n" + description; } @@ -207,7 +207,7 @@ void CRClickPopup::createAndPush(const std::string & txt, const CInfoWindow::TCo GH.windows().createAndPushWindow(temp); } -void CRClickPopup::createAndPush(const std::string & txt, std::shared_ptr component) +void CRClickPopup::createAndPush(const std::string & txt, const std::shared_ptr & component) { CInfoWindow::TCompsInfo intComps; intComps.push_back(component); @@ -244,7 +244,7 @@ void CRClickPopup::createAndPush(const CGObjectInstance * obj, const Point & p, } } -CRClickPopupInt::CRClickPopupInt(std::shared_ptr our) +CRClickPopupInt::CRClickPopupInt(const std::shared_ptr & our) { CCS->curh->hide(); defActions = SHOWALL | UPDATE; diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h index da1f7a154..5da10014b 100644 --- a/client/windows/InfoWindows.h +++ b/client/windows/InfoWindows.h @@ -47,7 +47,7 @@ public: void sliderMoved(int to); - CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo & comps = TCompsInfo(), const TButtonsInfo & Buttons = TButtonsInfo()); + CInfoWindow(const std::string & Text, PlayerColor player, const TCompsInfo & comps = TCompsInfo(), const TButtonsInfo & Buttons = TButtonsInfo()); CInfoWindow(); ~CInfoWindow(); @@ -57,7 +57,7 @@ public: static std::shared_ptr create(const std::string & text, PlayerColor playerID = PlayerColor(1), const TCompsInfo & components = TCompsInfo()); /// create text from title and description: {title}\n\n description - static std::string genText(std::string title, std::string description); + static std::string genText(const std::string & title, const std::string & description); }; /// popup displayed on R-click @@ -69,7 +69,7 @@ public: static std::shared_ptr createCustomInfoWindow(Point position, const CGObjectInstance * specific); static void createAndPush(const std::string & txt, const CInfoWindow::TCompsInfo & comps = CInfoWindow::TCompsInfo()); - static void createAndPush(const std::string & txt, std::shared_ptr component); + static void createAndPush(const std::string & txt, const std::shared_ptr & component); static void createAndPush(const CGObjectInstance * obj, const Point & p, ETextAlignment alignment = ETextAlignment::BOTTOMRIGHT); }; @@ -79,8 +79,8 @@ class CRClickPopupInt : public CRClickPopup std::shared_ptr inner; public: - CRClickPopupInt(std::shared_ptr our); - virtual ~CRClickPopupInt(); + CRClickPopupInt(const std::shared_ptr & our); + ~CRClickPopupInt(); }; /// popup on adventure map for town\hero and other objects with customized popup content From b0b3b9bb84d3475a6295065c98b4ff09a4c62dfc Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 28 Feb 2024 22:25:14 +0100 Subject: [PATCH 169/250] fix castle replay --- server/battles/BattleResultProcessor.cpp | 2 +- server/queries/BattleQueries.cpp | 17 +++++++---------- server/queries/BattleQueries.h | 5 ++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index 39af62295..e4b851331 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -282,7 +282,7 @@ void BattleResultProcessor::endBattle(const CBattleInfoCallback & battle) // in battles against neutrals attacker can ask to replay battle manually, additionally in battles against AI player human side can also ask for replay if(onlyOnePlayerHuman) { - auto battleDialogQuery = std::make_shared(gameHandler, battle.getBattle()); + auto battleDialogQuery = std::make_shared(gameHandler, battle.getBattle(), battleQuery->result); battleResult->queryID = battleDialogQuery->queryID; gameHandler->queries->addQuery(battleDialogQuery); } diff --git a/server/queries/BattleQueries.cpp b/server/queries/BattleQueries.cpp index f5e104f1a..9d076aebb 100644 --- a/server/queries/BattleQueries.cpp +++ b/server/queries/BattleQueries.cpp @@ -19,17 +19,10 @@ #include "../../lib/battle/SideInBattle.h" #include "../../lib/CPlayerState.h" #include "../../lib/mapObjects/CGObjectInstance.h" +#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/networkPacks/PacksForServer.h" #include "../../lib/serializer/Cast.h" -void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const -{ - assert(result); - - if(result) - objectVisit.visitedObject->battleFinished(objectVisit.visitingHero, *result); -} - CBattleQuery::CBattleQuery(CGameHandler * owner, const IBattleInfo * bi): CQuery(owner), battleID(bi->getBattleID()) @@ -75,9 +68,10 @@ void CBattleQuery::onExposure(QueryPtr topQuery) owner->popQuery(*this); } -CBattleDialogQuery::CBattleDialogQuery(CGameHandler * owner, const IBattleInfo * bi): +CBattleDialogQuery::CBattleDialogQuery(CGameHandler * owner, const IBattleInfo * bi, std::optional Br): CDialogQuery(owner), - bi(bi) + bi(bi), + result(Br) { addPlayer(bi->getSidePlayer(0)); addPlayer(bi->getSidePlayer(1)); @@ -104,6 +98,9 @@ void CBattleDialogQuery::onRemoval(PlayerColor color) } else { + if(result && bi->getDefendedTown()) + bi->getDefendedTown()->battleFinished(bi->getSideHero(BattleSide::ATTACKER), *result); + gh->battles->endBattleConfirm(bi->getBattleID()); } } diff --git a/server/queries/BattleQueries.h b/server/queries/BattleQueries.h index 871b62e5d..41d92ec98 100644 --- a/server/queries/BattleQueries.h +++ b/server/queries/BattleQueries.h @@ -28,7 +28,6 @@ public: CBattleQuery(CGameHandler * owner); CBattleQuery(CGameHandler * owner, const IBattleInfo * Bi); //TODO - void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; bool blocksPack(const CPack *pack) const override; void onRemoval(PlayerColor color) override; void onExposure(QueryPtr topQuery) override; @@ -37,9 +36,9 @@ public: class CBattleDialogQuery : public CDialogQuery { public: - CBattleDialogQuery(CGameHandler * owner, const IBattleInfo * Bi); + CBattleDialogQuery(CGameHandler * owner, const IBattleInfo * Bi, std::optional Br); const IBattleInfo * bi; - + std::optional result; void onRemoval(PlayerColor color) override; }; From 37f621abbde17038af539bad6933f5163b52984f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 28 Feb 2024 23:13:51 +0100 Subject: [PATCH 170/250] fix visiting objects other than town --- server/CGameHandler.cpp | 13 +++++++++++++ server/CGameHandler.h | 1 + server/queries/BattleQueries.cpp | 9 ++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 7b7fca060..2cb20219b 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4194,6 +4194,19 @@ const CGHeroInstance * CGameHandler::getVisitingHero(const CGObjectInstance *obj return nullptr; } +const CGObjectInstance * CGameHandler::getVisitingObject(const CGHeroInstance *hero) +{ + assert(hero); + + for(const auto & query : queries->allQueries()) + { + auto visit = std::dynamic_pointer_cast(query); + if (visit && visit->visitingHero == hero) + return visit->visitedObject; + } + return nullptr; +} + bool CGameHandler::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero) { assert(obj); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 67b9bf8d3..dc6bdd3e1 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -154,6 +154,7 @@ public: /// Returns hero that is currently visiting this object, or nullptr if no visit is active const CGHeroInstance * getVisitingHero(const CGObjectInstance *obj); + const CGObjectInstance * getVisitingObject(const CGHeroInstance *hero); bool isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero) override; void setObjPropertyValue(ObjectInstanceID objid, ObjProperty prop, int32_t value) override; void setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) override; diff --git a/server/queries/BattleQueries.cpp b/server/queries/BattleQueries.cpp index 9d076aebb..92e37fd6f 100644 --- a/server/queries/BattleQueries.cpp +++ b/server/queries/BattleQueries.cpp @@ -98,9 +98,12 @@ void CBattleDialogQuery::onRemoval(PlayerColor color) } else { - if(result && bi->getDefendedTown()) - bi->getDefendedTown()->battleFinished(bi->getSideHero(BattleSide::ATTACKER), *result); - + auto hero = bi->getSideHero(BattleSide::ATTACKER); + auto visitingObj = bi->getDefendedTown() ? bi->getDefendedTown() : gh->getVisitingObject(hero); + gh->battles->endBattleConfirm(bi->getBattleID()); + + if(visitingObj) + visitingObj->battleFinished(hero, *result); } } From 2c32c770f7b7fce90fd339c203b67877678b1fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Thu, 29 Feb 2024 12:45:08 +0100 Subject: [PATCH 171/250] First version that works in lobby --- client/lobby/CLobbyScreen.cpp | 6 ++++ client/lobby/RandomMapTab.cpp | 53 ++++++++++++++++++++++++++++++++++- client/lobby/RandomMapTab.h | 8 ++++-- client/lobby/SelectionTab.cpp | 1 + client/widgets/ComboBox.h | 3 +- lib/CMakeLists.txt | 1 + lib/rmg/CMapGenOptions.cpp | 42 +++++++++++++++++++++++++++ lib/rmg/CMapGenOptions.h | 2 ++ lib/rmg/CRmgTemplate.cpp | 28 +++++++++--------- mapeditor/windownewmap.cpp | 11 ++++++++ mapeditor/windownewmap.h | 17 +++++++---- 11 files changed, 148 insertions(+), 24 deletions(-) diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index 63cda2fd1..f08aacdfe 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -156,6 +156,12 @@ void CLobbyScreen::startCampaign() void CLobbyScreen::startScenario(bool allowOnlyAI) { + if (tabRand && CSH->si->mapGenOptions) + { + // Save RMG settings at game start + tabRand->saveOptions(*CSH->si->mapGenOptions); + } + if (CSH->validateGameStart(allowOnlyAI)) { CSH->sendStartGame(allowOnlyAI); diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 2d389095a..b6a7afb3c 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -38,6 +38,11 @@ #include "../../lib/filesystem/Filesystem.h" #include "../../lib/RoadHandler.h" +//#include "../../lib/GameSettings.h" +#include "../../lib/CConfigHandler.h" +#include "../../lib/serializer/JsonSerializer.h" +#include "../../lib/serializer/JsonDeserializer.h" + RandomMapTab::RandomMapTab(): InterfaceObjectConfigurable() { @@ -162,7 +167,8 @@ RandomMapTab::RandomMapTab(): }; } - updateMapInfoByHost(); + loadOptions(); + //updateMapInfoByHost(); } void RandomMapTab::updateMapInfoByHost() @@ -569,3 +575,48 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab): buttonOk = widget("buttonOK"); buttonCancel = widget("buttonCancel"); } + +void RandomMapTab::saveOptions(const CMapGenOptions & options) +{ + JsonNode data; + JsonSerializer ser(nullptr, data); + + ser.serializeStruct("lastSettings", const_cast(options)); + + // FIXME: Do not nest fields + Settings rmgSettings = persistentStorage.write["rmg"]; + rmgSettings["rmg"] = data; +} + +void RandomMapTab::loadOptions() +{ + // FIXME: Potential leak? + auto options = new CMapGenOptions(); + + + auto rmgSettings = persistentStorage["rmg"]["rmg"]; + if (!rmgSettings.Struct().empty()) + { + JsonDeserializer handler(nullptr, rmgSettings); + handler.serializeStruct("lastSettings", *options); + + // FIXME: Regenerate players, who are not saved + mapGenOptions.reset(options); + // Will check template and set other options as well + setTemplate(mapGenOptions->getMapTemplate()); + if(auto w = widget("templateList")) + { + // FIXME: Private function, need id + w->setItem(mapGenOptions->getMapTemplate()); + logGlobal->warn("Set RMG template on drop-down list"); + } + + // TODO: Else? Set default + logGlobal->warn("Loaded previous RMG settings"); + } + else + { + logGlobal->warn("Did not load previous RMG settings"); + } + updateMapInfoByHost(); +} \ No newline at end of file diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h index 2ad559e80..a18f8acfc 100644 --- a/client/lobby/RandomMapTab.h +++ b/client/lobby/RandomMapTab.h @@ -15,6 +15,7 @@ #include "../../lib/GameConstants.h" #include "../../lib/rmg/CRmgTemplate.h" #include "../gui/InterfaceObjectConfigurable.h" +#include "../lib/rmg/MapGenOptionsSaver.h" VCMI_LIB_NAMESPACE_BEGIN @@ -28,7 +29,7 @@ class CLabelGroup; class CSlider; class CPicture; -class RandomMapTab : public InterfaceObjectConfigurable +class RandomMapTab : public InterfaceObjectConfigurable, public MapGenOptionsSaver { public: RandomMapTab(); @@ -36,6 +37,9 @@ public: void updateMapInfoByHost(); void setMapGenOptions(std::shared_ptr opts); void setTemplate(const CRmgTemplate *); + + void saveOptions(const CMapGenOptions & options) override; + void loadOptions() override; CMapGenOptions & obtainMapGenOptions() {return *mapGenOptions;} CFunctionList, std::shared_ptr)> mapInfoChanged; @@ -44,8 +48,8 @@ private: void deactivateButtonsFrom(CToggleGroup & group, const std::set & allowed); std::vector getPossibleMapSizes(); - std::shared_ptr mapGenOptions; std::shared_ptr mapInfo; + std::shared_ptr mapGenOptions; //options allowed - need to store as impact each other std::set playerCountAllowed; diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 85e014c88..55858e72c 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -167,6 +167,7 @@ SelectionTab::SelectionTab(ESelectionScreen Type) inputName->filters += CTextInput::filenameFilter; labelMapSizes = std::make_shared(87, 62, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[510]); + // TODO: Global constants? int sizes[] = {36, 72, 108, 144, 0}; const char * filterIconNmes[] = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"}; for(int i = 0; i < 5; i++) diff --git a/client/widgets/ComboBox.h b/client/widgets/ComboBox.h index cd3c1e883..670734c10 100644 --- a/client/widgets/ComboBox.h +++ b/client/widgets/ComboBox.h @@ -51,8 +51,6 @@ class ComboBox : public CButton }; friend class DropDown; - - void setItem(const void *); public: ComboBox(Point position, const AnimationPath & defName, const std::pair & help, const JsonNode & dropDownDescriptor, Point dropDownPosition, EShortcut key = {}, bool playerColoredButton = false); @@ -67,6 +65,7 @@ public: std::function getItemText; void setItem(int id); + void setItem(const void *); void updateListItems(); }; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 566c2fdac..3740de13b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -534,6 +534,7 @@ set(lib_HEADERS rmg/RmgPath.h rmg/CMapGenerator.h rmg/CMapGenOptions.h + rmg/MapGenOptionsSaver.h rmg/CRmgTemplate.h rmg/CRmgTemplateStorage.h rmg/CZonePlacer.h diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index fe233bf99..26aef7842 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -17,6 +17,7 @@ #include "CRandomGenerator.h" #include "../VCMI_Lib.h" #include "../CTownHandler.h" +#include "serializer/JsonSerializeFormat.h" VCMI_LIB_NAMESPACE_BEGIN @@ -816,4 +817,45 @@ void CMapGenOptions::CPlayerSettings::setTeam(const TeamID & value) team = value; } +void CMapGenOptions::serializeJson(JsonSerializeFormat & handler) +{ + handler.serializeInt("width", width); + handler.serializeInt("height", height); + handler.serializeBool("haswoLevels", hasTwoLevels); + handler.serializeInt("humanOrCpuPlayerCount", humanOrCpuPlayerCount); + handler.serializeInt("teamCount", teamCount); + handler.serializeInt("compOnlyPlayerCount", compOnlyPlayerCount); + handler.serializeInt("compOnlyTeamCount", compOnlyTeamCount); + handler.serializeInt("waterContent", waterContent); + handler.serializeInt("monsterStrength", monsterStrength); + + std::string templateName; + if(mapTemplate && handler.saving) + { + templateName = mapTemplate->getId(); + } + handler.serializeString("templateName", templateName); + if(!handler.saving) + { + // FIXME: doesn't load correctly? Name is "Jebus Cross" + setMapTemplate(templateName); + if (mapTemplate) + { + logGlobal->warn("Loaded previous RMG template"); + // FIXME: Update dropdown menu + } + else + { + logGlobal->warn("Failed to deserialize previous map template"); + } + } + + handler.serializeIdArray("roads", enabledRoads); + //TODO: Serialize CMapGenOptions::CPlayerSettings ? This won't b saved between sessions + if (!handler.saving) + { + resetPlayersMap(); + } +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/CMapGenOptions.h b/lib/rmg/CMapGenOptions.h index 47e33c797..2055d58e5 100644 --- a/lib/rmg/CMapGenOptions.h +++ b/lib/rmg/CMapGenOptions.h @@ -210,6 +210,8 @@ public: h & enabledRoads; } + + void serializeJson(JsonSerializeFormat & handler); }; VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 75467694f..ac7a491e3 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -841,20 +841,20 @@ void CRmgTemplate::serializeSize(JsonSerializeFormat & handler, int3 & value, co { static const std::map sizeMapping = { - {"s", { 36, 36, 1}}, - {"s+u", { 36, 36, 2}}, - {"m", { 72, 72, 1}}, - {"m+u", { 72, 72, 2}}, - {"l", {108, 108, 1}}, - {"l+u", {108, 108, 2}}, - {"xl", {144, 144, 1}}, - {"xl+u", {144, 144, 2}}, - {"h", {180, 180, 1}}, - {"h+u", {180, 180, 2}}, - {"xh", {216, 216, 1}}, - {"xh+u", {216, 216, 2}}, - {"g", {252, 252, 1}}, - {"g+u", {252, 252, 2}} + {"s", {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_SMALL, 1}}, + {"s+u", {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_SMALL, 2}}, + {"m", {CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_MIDDLE, 1}}, + {"m+u", {CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_MIDDLE, 2}}, + {"l", {CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_LARGE, 1}}, + {"l+u", {CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_LARGE, 2}}, + {"xl", {CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_XLARGE, 1}} , + {"xl+u", {CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_XLARGE, 2}} , + {"h", {CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_HUGE, 1}}, + {"h+u", {CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_HUGE, 2}}, + {"xh", {CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_XHUGE, 1}}, + {"xh+u", {CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_XHUGE, 2}}, + {"g", {CMapHeader::MAP_SIZE_GIANT, CMapHeader::MAP_SIZE_GIANT, 1}}, + {"g+u", {CMapHeader::MAP_SIZE_GIANT, CMapHeader::MAP_SIZE_GIANT, 2}} }; static const std::map sizeReverseMapping = vstd::invertMap(sizeMapping); diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index cc673b4bd..e3ff7d798 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -219,6 +219,17 @@ void WindowNewMap::saveUserSettings() } } +void WindowNewMap::saveOptions(const CMapGenOptions & options) +{ + // TODO +} + +void WindowNewMap::loadOptions() +{ + mapGenOptions = CMapGenOptions(); + // TODO +} + void WindowNewMap::on_cancelButton_clicked() { close(); diff --git a/mapeditor/windownewmap.h b/mapeditor/windownewmap.h index 76bd5dc22..5622ef781 100644 --- a/mapeditor/windownewmap.h +++ b/mapeditor/windownewmap.h @@ -12,13 +12,14 @@ #include #include "../lib/rmg/CMapGenOptions.h" +#include "../lib/rmg/MapGenOptionsSaver.h" namespace Ui { class WindowNewMap; } -class WindowNewMap : public QDialog +class WindowNewMap : public QDialog, public MapGenOptionsSaver { Q_OBJECT @@ -64,10 +65,13 @@ class WindowNewMap : public QDialog const std::map> mapSizes { - {0, {36, 36}}, - {1, {72, 72}}, - {2, {108, 108}}, - {3, {144, 144}}, + {0, {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_SMALL}}, + {1, {CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_MIDDLE}}, + {2, {CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_LARGE}}, + {3, {CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_XLARGE}}, + {4, {CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_HUGE}}, + {5, {CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_XHUGE}}, + {6, {CMapHeader::MAP_SIZE_GIANT, CMapHeader::MAP_SIZE_GIANT}}, }; public: @@ -108,6 +112,9 @@ private: void loadUserSettings(); void saveUserSettings(); + void saveOptions(const CMapGenOptions & options) override; + void loadOptions() override; + private: Ui::WindowNewMap *ui; From 07d201502eb8d325b6972193a64b0ca12d661776 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 27 Feb 2024 22:19:09 +0200 Subject: [PATCH 172/250] Refactoring of button class to prepare for configurable button --- client/NetPacksLobbyClient.cpp | 2 +- client/battle/BattleInterfaceClasses.h | 1 - client/battle/BattleWindow.cpp | 5 +- client/globalLobby/GlobalLobbyLoginWindow.cpp | 4 +- client/globalLobby/GlobalLobbyServerSetup.cpp | 8 +- client/gui/InterfaceObjectConfigurable.cpp | 6 +- client/lobby/CBonusSelection.cpp | 4 +- client/lobby/CLobbyScreen.cpp | 16 +- client/lobby/OptionsTab.cpp | 2 +- client/lobby/RandomMapTab.cpp | 12 +- client/lobby/SelectionTab.cpp | 2 +- client/mainmenu/CCampaignScreen.cpp | 2 +- client/mainmenu/CMainMenu.cpp | 2 +- client/widgets/Buttons.cpp | 200 +++++++++--------- client/widgets/Buttons.h | 98 +++++---- client/widgets/ComboBox.cpp | 7 +- client/widgets/Slider.cpp | 8 +- client/windows/CCastleInterface.cpp | 4 +- client/windows/CCreatureWindow.cpp | 4 +- client/windows/CHeroWindow.cpp | 6 +- client/windows/CKingdomInterface.cpp | 6 +- client/windows/GUIClasses.cpp | 31 ++- 22 files changed, 223 insertions(+), 207 deletions(-) diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index 6b7108527..b36f2f305 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -102,7 +102,7 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyChatMessage(LobbyChatMessage & lobby->card->chat->addNewMessage(pack.playerName + ": " + pack.message); lobby->card->setChat(true); if(lobby->buttonChat) - lobby->buttonChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL, Colors::WHITE); + lobby->buttonChat->setTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL, Colors::WHITE); } } diff --git a/client/battle/BattleInterfaceClasses.h b/client/battle/BattleInterfaceClasses.h index 1a3d69ce1..d924d8347 100644 --- a/client/battle/BattleInterfaceClasses.h +++ b/client/battle/BattleInterfaceClasses.h @@ -35,7 +35,6 @@ class BattleInterface; class CPicture; class CFilledTexture; class CButton; -class CToggleButton; class CLabel; class CMultiLineLabel; class CTextBox; diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index 4a0365e79..790d09d04 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -563,9 +563,8 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action) iconName = AnimationPath::fromJson(variables["actionIconNoReturn"]); break; } - - auto anim = GH.renderHandler().loadAnimation(iconName); - w->setImage(anim); + + w->setImage(iconName); w->redraw(); } diff --git a/client/globalLobby/GlobalLobbyLoginWindow.cpp b/client/globalLobby/GlobalLobbyLoginWindow.cpp index bf5c68224..02fbcc342 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.cpp +++ b/client/globalLobby/GlobalLobbyLoginWindow.cpp @@ -47,8 +47,8 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow() 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); + buttonRegister->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.login.create"), EFonts::FONT_SMALL, Colors::YELLOW); + buttonLogin->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.login.login"), EFonts::FONT_SMALL, Colors::YELLOW); toggleMode = std::make_shared(nullptr); toggleMode->addToggle(0, buttonRegister); diff --git a/client/globalLobby/GlobalLobbyServerSetup.cpp b/client/globalLobby/GlobalLobbyServerSetup.cpp index eb566390f..3e1caa98a 100644 --- a/client/globalLobby/GlobalLobbyServerSetup.cpp +++ b/client/globalLobby/GlobalLobbyServerSetup.cpp @@ -50,8 +50,8 @@ GlobalLobbyServerSetup::GlobalLobbyServerSetup() 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); + buttonPublic->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.public"), EFonts::FONT_SMALL, Colors::YELLOW); + buttonPrivate->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.private"), EFonts::FONT_SMALL, Colors::YELLOW); toggleRoomType = std::make_shared(nullptr); toggleRoomType->addToggle(0, buttonPublic); @@ -61,8 +61,8 @@ GlobalLobbyServerSetup::GlobalLobbyServerSetup() 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); + buttonNewGame->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.new"), EFonts::FONT_SMALL, Colors::YELLOW); + buttonLoadGame->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.load"), EFonts::FONT_SMALL, Colors::YELLOW); toggleGameMode = std::make_shared(nullptr); toggleGameMode->addToggle(0, buttonNewGame); diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index 89887d0ff..320d999d9 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -396,7 +396,7 @@ std::shared_ptr InterfaceObjectConfigurable::buildToggleButton(co { for(const auto & item : config["items"].Vector()) { - button->addOverlay(buildWidget(item)); + button->setOverlay(buildWidget(item)); } } if(!config["selected"].isNull()) @@ -422,7 +422,7 @@ std::shared_ptr InterfaceObjectConfigurable::buildButton(const JsonNode { for(const auto & item : config["items"].Vector()) { - button->addOverlay(buildWidget(item)); + button->setOverlay(buildWidget(item)); } } if(!config["imageOrder"].isNull()) @@ -589,7 +589,7 @@ std::shared_ptr InterfaceObjectConfigurable::buildComboBox(const JsonN { for(const auto & item : config["items"].Vector()) { - result->addOverlay(buildWidget(item)); + result->setOverlay(buildWidget(item)); } } if(!config["imageOrder"].isNull()) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index c000f0a0e..7ce3b4b7b 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -126,7 +126,7 @@ CBonusSelection::CBonusSelection() tabExtraOptions->recreate(true); tabExtraOptions->setEnabled(false); 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); + buttonExtraOptions->setTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.extraOptions.hover"), FONT_SMALL, Colors::WHITE); } } @@ -306,7 +306,7 @@ void CBonusSelection::createBonusesIcons() auto anim = GH.renderHandler().createAnimation(); anim->setCustom(picName, 0); - bonusButton->setImage(anim); +//TODO: bonusButton->setImage(anim); if(CSH->campaignBonus == i) bonusButton->setBorderColor(Colors::BRIGHT_YELLOW); groupBonuses->addToggle(i, bonusButton); diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index 63cda2fd1..4cd6c94f6 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -61,7 +61,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType) }; buttonChat = std::make_shared(Point(619, 80), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT); - buttonChat->addTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL, Colors::WHITE); + buttonChat->setTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL, Colors::WHITE); switch(screenType) { @@ -171,18 +171,18 @@ void CLobbyScreen::toggleMode(bool host) return; auto buttonColor = host ? Colors::WHITE : Colors::ORANGE; - buttonSelect->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor); - buttonOptions->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, buttonColor); + buttonSelect->setTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor); + buttonOptions->setTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, buttonColor); if (buttonTurnOptions) - buttonTurnOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.turnOptions.hover"), FONT_SMALL, buttonColor); + buttonTurnOptions->setTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.turnOptions.hover"), FONT_SMALL, buttonColor); if (buttonExtraOptions) - buttonExtraOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.extraOptions.hover"), FONT_SMALL, buttonColor); + buttonExtraOptions->setTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.extraOptions.hover"), FONT_SMALL, buttonColor); if(buttonRMG) { - buttonRMG->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor); + buttonRMG->setTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor); buttonRMG->block(!host); } buttonSelect->block(!host); @@ -206,9 +206,9 @@ void CLobbyScreen::toggleChat() { card->toggleChat(); if(card->showChat) - buttonChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL, Colors::WHITE); + buttonChat->setTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL, Colors::WHITE); else - buttonChat->addTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL, Colors::WHITE); + buttonChat->setTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL, Colors::WHITE); } void CLobbyScreen::updateAfterStateChange() diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 2f1dc5cbb..0d4f1a0d2 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -917,7 +917,7 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con CGI->generaltexth->zelp[180], std::bind(&OptionsTab::onSetPlayerClicked, &parentTab, *s) ); - flag->hoverable = true; + flag->setHoverable(true); flag->block(CSH->isGuest()); } else diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 2d389095a..4ff10a5b6 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -364,9 +364,9 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr opts) if(auto w = widget("templateButton")) { if(tmpl) - w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL, Colors::WHITE); + w->setTextOverlay(tmpl->getName(), EFonts::FONT_SMALL, Colors::WHITE); else - w->addTextOverlay(readText(variables["randomTemplate"]), EFonts::FONT_SMALL, Colors::WHITE); + w->setTextOverlay(readText(variables["randomTemplate"]), EFonts::FONT_SMALL, Colors::WHITE); } for(auto r : VLC->roadTypeHandler->objects) { @@ -388,9 +388,9 @@ void RandomMapTab::setTemplate(const CRmgTemplate * tmpl) if(auto w = widget("templateButton")) { if(tmpl) - w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL, Colors::WHITE); + w->setTextOverlay(tmpl->getName(), EFonts::FONT_SMALL, Colors::WHITE); else - w->addTextOverlay(readText(variables["randomTemplate"]), EFonts::FONT_SMALL, Colors::WHITE); + w->setTextOverlay(readText(variables["randomTemplate"]), EFonts::FONT_SMALL, Colors::WHITE); } updateMapInfoByHost(); } @@ -532,11 +532,11 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab): assert(button); if(sel == teamId) { - button->addOverlay(buildWidget(variables["flagsAnimation"])); + button->setOverlay(buildWidget(variables["flagsAnimation"])); } else { - button->addOverlay(nullptr); + button->setOverlay(nullptr); } button->addCallback([this](bool) { diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 85e014c88..f0fd39701 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -217,7 +217,7 @@ SelectionTab::SelectionTab(ESelectionScreen Type) if(enableUiEnhancements) { buttonsSortBy.push_back(std::make_shared(Point(371, 85), AnimationPath::builtin("lobby/selectionTabSortDate"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.sortDate")), std::bind(&SelectionTab::sortBy, this, ESortBy::_changeDate))); - buttonsSortBy.back()->setAnimateLonelyFrame(true); +//TODO: buttonsSortBy.back()->setAnimateLonelyFrame(true); } iconsMapFormats = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCSELC.DEF")); diff --git a/client/mainmenu/CCampaignScreen.cpp b/client/mainmenu/CCampaignScreen.cpp index 4078908e1..9db6cf79c 100644 --- a/client/mainmenu/CCampaignScreen.cpp +++ b/client/mainmenu/CCampaignScreen.cpp @@ -66,7 +66,7 @@ CCampaignScreen::CCampaignScreen(const JsonNode & config, std::string name) if(!config[name]["exitbutton"].isNull()) { buttonBack = createExitButton(config[name]["exitbutton"]); - buttonBack->hoverable = true; + buttonBack->setHoverable(true); } for(const JsonNode & node : config[name]["items"].Vector()) diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index f36e0718e..f4e91daca 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -260,7 +260,7 @@ CMenuEntry::CMenuEntry(CMenuScreen * parent, const JsonNode & config) for(const JsonNode & node : config["buttons"].Vector()) { buttons.push_back(createButton(parent, node)); - buttons.back()->hoverable = true; + buttons.back()->setHoverable(true); buttons.back()->setRedrawParent(true); } } diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp index b4706fa3a..d31ce2d9e 100644 --- a/client/widgets/Buttons.cpp +++ b/client/widgets/Buttons.cpp @@ -30,30 +30,25 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/CGeneralTextHandler.h" -void CButton::update() +void ButtonBase::update() { if (overlay) { Point targetPos = Rect::createCentered( pos, overlay->pos.dimensions()).topLeft(); - if (state == PRESSED) + if (state == EButtonState::PRESSED) overlay->moveTo(targetPos + Point(1,1)); else overlay->moveTo(targetPos); } int newPos = stateToIndex[int(state)]; - if(animateLonelyFrame) - { - if(state == PRESSED) - image->moveBy(Point(1,1)); - else - image->moveBy(Point(-1,-1)); - } if (newPos < 0) newPos = 0; - if (state == HIGHLIGHTED && image->size() < 4) + // checkbox - has only have two frames: normal and pressed/highlighted + // hero movement speed buttons: only three frames: normal, pressed and blocked/highlighted + if (state == EButtonState::HIGHLIGHTED && image->size() < 4) newPos = (int)image->size()-1; image->setFrame(newPos); @@ -71,14 +66,14 @@ void CButton::addCallback(const std::function & callback) this->callback += callback; } -void CButton::addTextOverlay(const std::string & Text, EFonts font, ColorRGBA color) +void ButtonBase::setTextOverlay(const std::string & Text, EFonts font, ColorRGBA color) { OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE); - addOverlay(std::make_shared(pos.w/2, pos.h/2, font, ETextAlignment::CENTER, color, Text)); + setOverlay(std::make_shared(pos.w/2, pos.h/2, font, ETextAlignment::CENTER, color, Text)); update(); } -void CButton::addOverlay(std::shared_ptr newOverlay) +void ButtonBase::setOverlay(std::shared_ptr newOverlay) { overlay = newOverlay; if(overlay) @@ -90,17 +85,23 @@ void CButton::addOverlay(std::shared_ptr newOverlay) update(); } -void CButton::addImage(const AnimationPath & filename) +void ButtonBase::setImage(const AnimationPath & defName, bool playerColoredButton) { - imageNames.push_back(filename); + OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE); + + image = std::make_shared(defName, vstd::to_underlying(getState())); + pos = image->pos; + + if (playerColoredButton) + image->playerColored(LOCPLINT->playerID); } -void CButton::addHoverText(ButtonState state, std::string text) +void CButton::addHoverText(EButtonState state, std::string text) { - hoverTexts[state] = text; + hoverTexts[vstd::to_underlying(state)] = text; } -void CButton::setImageOrder(int state1, int state2, int state3, int state4) +void ButtonBase::setImageOrder(int state1, int state2, int state3, int state4) { stateToIndex[0] = state1; stateToIndex[1] = state2; @@ -109,44 +110,65 @@ void CButton::setImageOrder(int state1, int state2, int state3, int state4) update(); } -void CButton::setAnimateLonelyFrame(bool agreement) +//TODO: +//void CButton::setAnimateLonelyFrame(bool agreement) +//{ +// animateLonelyFrame = agreement; +//} + +void ButtonBase::setStateImpl(EButtonState newState) { - animateLonelyFrame = agreement; -} -void CButton::setState(ButtonState newState) -{ - if (state == newState) - return; - - if (newState == BLOCKED) - removeUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD); - else - addUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD); - - state = newState; update(); } -CButton::ButtonState CButton::getState() +void CButton::setState(EButtonState newState) +{ + if (getState() == newState) + return; + + if (newState == EButtonState::BLOCKED) + removeUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD); + else + addUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD); + + setStateImpl(newState); +} + +EButtonState ButtonBase::getState() { return state; } bool CButton::isBlocked() { - return state == BLOCKED; + return getState() == EButtonState::BLOCKED; } bool CButton::isHighlighted() { - return state == HIGHLIGHTED; + return getState() == EButtonState::HIGHLIGHTED; +} + +void CButton::setHoverable(bool on) +{ + hoverable = on; +} + +void CButton::setSoundDisabled(bool on) +{ + soundDisabled = on; +} + +void CButton::setActOnDown(bool on) +{ + actOnDown = on; } void CButton::block(bool on) { - if(on || state == BLOCKED) //dont change button state if unblock requested, but it's not blocked - setState(on ? BLOCKED : NORMAL); + if(on || getState() == EButtonState::BLOCKED) //dont change button state if unblock requested, but it's not blocked + setState(on ? EButtonState::BLOCKED : EButtonState::NORMAL); } void CButton::onButtonClicked() @@ -169,14 +191,14 @@ void CButton::clickPressed(const Point & cursorPosition) if(isBlocked()) return; - if (getState() != PRESSED) + if (getState() != EButtonState::PRESSED) { if (!soundDisabled) { CCS->soundh->playSound(soundBase::button); GH.input().hapticFeedback(); } - setState(PRESSED); + setState(EButtonState::PRESSED); if (actOnDown) onButtonClicked(); @@ -185,12 +207,12 @@ void CButton::clickPressed(const Point & cursorPosition) void CButton::clickReleased(const Point & cursorPosition) { - if (getState() == PRESSED) + if (getState() == EButtonState::PRESSED) { if(hoverable && isHovered()) - setState(HIGHLIGHTED); + setState(EButtonState::HIGHLIGHTED); else - setState(NORMAL); + setState(EButtonState::NORMAL); if (!actOnDown) onButtonClicked(); @@ -199,12 +221,12 @@ void CButton::clickReleased(const Point & cursorPosition) void CButton::clickCancel(const Point & cursorPosition) { - if (getState() == PRESSED) + if (getState() == EButtonState::PRESSED) { if(hoverable && isHovered()) - setState(HIGHLIGHTED); + setState(EButtonState::HIGHLIGHTED); else - setState(NORMAL); + setState(EButtonState::NORMAL); } } @@ -219,17 +241,17 @@ void CButton::hover (bool on) if(hoverable && !isBlocked()) { if(on) - setState(HIGHLIGHTED); + setState(EButtonState::HIGHLIGHTED); else - setState(NORMAL); + setState(EButtonState::NORMAL); } /*if(pressedL && on) // WTF is this? When this is used? setState(PRESSED);*/ - std::string name = hoverTexts[getState()].empty() + std::string name = hoverTexts[vstd::to_underlying(getState())].empty() ? hoverTexts[0] - : hoverTexts[getState()]; + : hoverTexts[vstd::to_underlying(getState())]; if(!name.empty() && !isBlocked()) //if there is no name, there is nothing to display also { @@ -240,55 +262,30 @@ void CButton::hover (bool on) } } +ButtonBase::ButtonBase(Point position, const AnimationPath & defName, EShortcut key, bool playerColoredButton) + : CKeyShortcut(key) + , stateToIndex({0, 1, 2, 3}) + , state(EButtonState::NORMAL) +{ + pos.x += position.x; + pos.y += position.y; + + setImage(defName); +} + CButton::CButton(Point position, const AnimationPath &defName, const std::pair &help, CFunctionList Callback, EShortcut key, bool playerColoredButton): - CKeyShortcut(key), + ButtonBase(position, defName, key, playerColoredButton), callback(Callback) { defActions = 255-DISPOSE; addUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD); - stateToIndex[0] = 0; - stateToIndex[1] = 1; - stateToIndex[2] = 2; - stateToIndex[3] = 3; - - state=NORMAL; - - currentImage = -1; hoverable = actOnDown = soundDisabled = false; hoverTexts[0] = help.first; helpBox=help.second; - - pos.x += position.x; - pos.y += position.y; - - if (!defName.empty()) - { - imageNames.push_back(defName); - setIndex(0); - if (playerColoredButton) - image->playerColored(LOCPLINT->playerID); - } } -void CButton::setIndex(size_t index) -{ - if (index == currentImage || index>=imageNames.size()) - return; - currentImage = index; - auto anim = GH.renderHandler().loadAnimation(imageNames[index]); - setImage(anim); -} - -void CButton::setImage(std::shared_ptr anim, int animFlags) -{ - OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE); - - image = std::make_shared(anim, getState(), 0, 0, 0, animFlags); - pos = image->pos; -} - -void CButton::setPlayerColor(PlayerColor player) +void ButtonBase::setPlayerColor(PlayerColor player) { if (image && image->isPlayerColored()) image->playerColored(player); @@ -353,6 +350,11 @@ void CToggleBase::setSelected(bool on) callback(on); } +bool CToggleBase::isSelected() const +{ + return selected; +} + bool CToggleBase::canActivate() { if (selected && !allowDeselection) @@ -365,29 +367,33 @@ void CToggleBase::addCallback(std::function function) callback += function; } +void CToggleBase::setAllowDeselection(bool on) +{ + allowDeselection = on; +} + CToggleButton::CToggleButton(Point position, const AnimationPath &defName, const std::pair &help, CFunctionList callback, EShortcut key, bool playerColoredButton): CButton(position, defName, help, 0, key, playerColoredButton), CToggleBase(callback) { - allowDeselection = true; } void CToggleButton::doSelect(bool on) { if (on) { - setState(HIGHLIGHTED); + setState(EButtonState::HIGHLIGHTED); } else { - setState(NORMAL); + setState(EButtonState::NORMAL); } } void CToggleButton::setEnabled(bool enabled) { - setState(enabled ? NORMAL : BLOCKED); + setState(enabled ? EButtonState::NORMAL : EButtonState::BLOCKED); } void CToggleButton::clickPressed(const Point & cursorPosition) @@ -403,7 +409,7 @@ void CToggleButton::clickPressed(const Point & cursorPosition) { CCS->soundh->playSound(soundBase::button); GH.input().hapticFeedback(); - setState(PRESSED); + setState(EButtonState::PRESSED); } } @@ -416,13 +422,13 @@ void CToggleButton::clickReleased(const Point & cursorPosition) if(isBlocked()) return; - if (getState() == PRESSED && canActivate()) + if (getState() == EButtonState::PRESSED && canActivate()) { onButtonClicked(); - setSelected(!selected); + setSelected(!isSelected()); } else - doSelect(selected); // restore + doSelect(isSelected()); // restore } void CToggleButton::clickCancel(const Point & cursorPosition) @@ -434,7 +440,7 @@ void CToggleButton::clickCancel(const Point & cursorPosition) if(isBlocked()) return; - doSelect(selected); + doSelect(isSelected()); } void CToggleGroup::addCallback(std::function callback) @@ -455,7 +461,7 @@ void CToggleGroup::addToggle(int identifier, std::shared_ptr button } button->addCallback([=] (bool on) { if (on) selectionChanged(identifier);}); - button->allowDeselection = false; + button->setAllowDeselection(false); if(buttons.count(identifier)>0) logAnim->error("Duplicated toggle button id %d", identifier); diff --git a/client/widgets/Buttons.h b/client/widgets/Buttons.h index 9eea639fd..da14e782b 100644 --- a/client/widgets/Buttons.h +++ b/client/widgets/Buttons.h @@ -22,64 +22,77 @@ class CAnimImage; class CLabel; class CAnimation; +enum class EButtonState +{ + NORMAL=0, + PRESSED=1, + BLOCKED=2, + HIGHLIGHTED=3 // used for: highlighted state for selectable buttons, hovered state for hoverable buttons (e.g. main menu) +}; + +class ButtonBase : public CKeyShortcut +{ + std::shared_ptr image; //image for this button + std::shared_ptr overlay;//object-overlay, can be null + + std::array stateToIndex; // mapping of button state to index of frame in animation + + EButtonState state;//current state of button from enum + + void update();//to refresh button after image or text change + +protected: + ButtonBase(Point position, const AnimationPath & defName, EShortcut key, bool playerColoredButton); + + void setStateImpl(EButtonState state); + EButtonState getState(); + +public: + /// Appearance modifiers + void setPlayerColor(PlayerColor player); + void setImage(const AnimationPath & defName, bool playerColoredButton = false); + void setImageOrder(int state1, int state2, int state3, int state4); + + /// adds overlay on top of button image. Only one overlay can be active at once + void setOverlay(std::shared_ptr newOverlay); + void setTextOverlay(const std::string & Text, EFonts font, ColorRGBA color); +}; + /// Typical Heroes 3 button which can be inactive or active and can /// hold further information if you right-click it -class CButton : public CKeyShortcut +class CButton : public ButtonBase { CFunctionList callback; -public: - enum ButtonState - { - NORMAL=0, - PRESSED=1, - BLOCKED=2, - HIGHLIGHTED=3 - }; -protected: - std::vector imageNames;//store list of images that can be used by this button - size_t currentImage; - - ButtonState state;//current state of button from enum - - std::array stateToIndex; // mapping of button state to index of frame in animation std::array hoverTexts; //texts for statusbar, if empty - first entry will be used std::optional borderColor; // mapping of button state to border color std::string helpBox; //for right-click help - std::shared_ptr image; //image for this button - std::shared_ptr overlay;//object-overlay, can be null - bool animateLonelyFrame = false; + bool actOnDown; //runs when mouse is pressed down over it, not when up + bool hoverable; //if true, button will be highlighted when hovered (e.g. main menu) + bool soundDisabled; + protected: void onButtonClicked(); // calls callback - void update();//to refresh button after image or text change // internal method to change state. Public change can be done only via block() - void setState(ButtonState newState); - ButtonState getState(); + void setState(EButtonState newState); public: - bool actOnDown,//runs when mouse is pressed down over it, not when up - hoverable,//if true, button will be highlighted when hovered (e.g. main menu) - soundDisabled; - // sets the same border color for all button states. void setBorderColor(std::optional borderColor); /// adds one more callback to on-click actions void addCallback(const std::function & callback); - /// adds overlay on top of button image. Only one overlay can be active at once - void addOverlay(std::shared_ptr newOverlay); - void addTextOverlay(const std::string & Text, EFonts font, ColorRGBA color); + void addHoverText(EButtonState state, std::string text); - void addImage(const AnimationPath & filename); - void addHoverText(ButtonState state, std::string text); - - void setImageOrder(int state1, int state2, int state3, int state4); - void setAnimateLonelyFrame(bool agreement); void block(bool on); + void setHoverable(bool on); + void setSoundDisabled(bool on); + void setActOnDown(bool on); + /// State modifiers bool isBlocked(); bool isHighlighted(); @@ -88,11 +101,6 @@ public: CButton(Point position, const AnimationPath & defName, const std::pair & help, CFunctionList Callback = 0, EShortcut key = {}, bool playerColoredButton = false ); - /// Appearance modifiers - void setIndex(size_t index); - void setImage(std::shared_ptr anim, int animFlags=0); - void setPlayerColor(PlayerColor player); - /// CIntObject overrides void showPopupWindow(const Point & cursorPosition) override; void clickPressed(const Point & cursorPosition) override; @@ -110,10 +118,13 @@ public: class CToggleBase { CFunctionList callback; -protected: bool selected; + /// if set to false - button can not be deselected normally + bool allowDeselection; + +protected: // internal method for overrides virtual void doSelect(bool on); @@ -121,9 +132,6 @@ protected: bool canActivate(); public: - /// if set to false - button can not be deselected normally - bool allowDeselection; - CToggleBase(CFunctionList callback); virtual ~CToggleBase(); @@ -133,6 +141,10 @@ public: /// Changes selection to "on" without calling callback void setSelectedSilent(bool on); + bool isSelected() const; + + void setAllowDeselection(bool on); + void addCallback(std::function callback); /// Set whether the toggle is currently enabled for user to use, this is only inplemented in ToggleButton, not for other toggles yet. diff --git a/client/widgets/ComboBox.cpp b/client/widgets/ComboBox.cpp index f0150b6d1..eed1e831f 100644 --- a/client/widgets/ComboBox.cpp +++ b/client/widgets/ComboBox.cpp @@ -168,10 +168,11 @@ ComboBox::ComboBox(Point position, const AnimationPath & defName, const std::pai void ComboBox::setItem(const void * item) { - auto w = std::dynamic_pointer_cast(overlay); + // TODO: + //auto w = std::dynamic_pointer_cast(overlay); - if( w && getItemText) - addTextOverlay(getItemText(0, item), w->font, w->color); + //if( w && getItemText) + // setTextOverlay(getItemText(0, item), w->font, w->color); if(onSetItem) onSetItem(item); diff --git a/client/widgets/Slider.cpp b/client/widgets/Slider.cpp index dff5d3229..68b81bbe8 100644 --- a/client/widgets/Slider.cpp +++ b/client/widgets/Slider.cpp @@ -206,10 +206,10 @@ CSlider::CSlider(Point position, int totalw, const std::function & Mo right = std::make_shared(Point(), AnimationPath::builtin(getOrientation() == Orientation::HORIZONTAL ? "SCNRBRT.DEF" : "SCNRBDN.DEF"), CButton::tooltip()); slider = std::make_shared(Point(), AnimationPath::builtin("SCNRBSL.DEF"), CButton::tooltip()); } - slider->actOnDown = true; - slider->soundDisabled = true; - left->soundDisabled = true; - right->soundDisabled = true; + slider->setActOnDown(true); + slider->setSoundDisabled(true); + left->setSoundDisabled(true); + right->setSoundDisabled(true); if (getOrientation() == Orientation::HORIZONTAL) right->moveBy(Point(totalw - right->pos.w, 0)); diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 16fdd2365..309b832a9 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1334,12 +1334,12 @@ void CCastleInterface::recreateIcons() fastTownHall = std::make_shared(Point(80, 413), AnimationPath::builtin("ITMTL.def"), CButton::tooltip(), [&](){ builds->enterTownHall(); }); fastTownHall->setImageOrder(town->hallLevel(), town->hallLevel(), town->hallLevel(), town->hallLevel()); - fastTownHall->setAnimateLonelyFrame(true); +//TODO: fastTownHall->setAnimateLonelyFrame(true); int imageIndex = town->fortLevel() == CGTownInstance::EFortLevel::NONE ? 3 : town->fortLevel() - 1; fastArmyPurchase = std::make_shared(Point(122, 413), AnimationPath::builtin("itmcl.def"), CButton::tooltip(), [&](){ builds->enterToTheQuickRecruitmentWindow(); }); fastArmyPurchase->setImageOrder(imageIndex, imageIndex, imageIndex, imageIndex); - fastArmyPurchase->setAnimateLonelyFrame(true); +//TODO: fastArmyPurchase->setAnimateLonelyFrame(true); fastMarket = std::make_shared(Rect(163, 410, 64, 42), [&]() { diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index 449c005bd..65b457733 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -340,7 +340,7 @@ CStackWindow::ButtonsSection::ButtonsSection(CStackWindow * owner, int yOffset) }; auto upgradeBtn = std::make_shared(Point(221 + (int)buttonIndex * 40, 5), AnimationPath::builtin("stackWindow/upgradeButton"), CGI->generaltexth->zelp[446], onClick); - upgradeBtn->addOverlay(std::make_shared(AnimationPath::builtin("CPRSMALL"), VLC->creh->objects[upgradeInfo.info.newID[buttonIndex]]->getIconIndex())); + upgradeBtn->setOverlay(std::make_shared(AnimationPath::builtin("CPRSMALL"), VLC->creh->objects[upgradeInfo.info.newID[buttonIndex]]->getIconIndex())); if(buttonsToCreate == 1) // single upgrade avaialbe upgradeBtn->assignedKey = EShortcut::RECRUITMENT_UPGRADE; @@ -366,7 +366,7 @@ CStackWindow::ButtonsSection::ButtonsSection(CStackWindow * owner, int yOffset) std::string tooltipText = "vcmi.creatureWindow." + btnIDs[buttonIndex]; parent->switchButtons[buttonIndex] = std::make_shared(Point(302 + (int)buttonIndex*40, 5), AnimationPath::builtin("stackWindow/upgradeButton"), CButton::tooltipLocalized(tooltipText), onSwitch); - parent->switchButtons[buttonIndex]->addOverlay(std::make_shared(AnimationPath::builtin("stackWindow/switchModeIcons"), buttonIndex)); + parent->switchButtons[buttonIndex]->setOverlay(std::make_shared(AnimationPath::builtin("stackWindow/switchModeIcons"), buttonIndex)); } parent->switchButtons[parent->activeTab]->disable(); } diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 3a5f21010..293fd1329 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -89,7 +89,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero) { questlogButton = std::make_shared(Point(314, 429), AnimationPath::builtin("hsbtns4.def"), CButton::tooltip(heroscrn[0]), [=](){ LOCPLINT->showQuestLog(); }, EShortcut::ADVENTURE_QUEST_LOG); backpackButton = std::make_shared(Point(424, 429), AnimationPath::builtin("buttons/backpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), [=](){ createBackpackWindow(); }, EShortcut::HERO_BACKPACK); - backpackButton->addOverlay(std::make_shared(ImagePath::builtin("buttons/backpackButtonIcon"))); + backpackButton->setOverlay(std::make_shared(ImagePath::builtin("buttons/backpackButtonIcon"))); dismissButton = std::make_shared(Point(534, 429), AnimationPath::builtin("hsbtns2.def"), CButton::tooltip(heroscrn[28]), [=](){ dismissCurrent(); }, EShortcut::HERO_DISMISS); } else @@ -197,9 +197,9 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded) specName->setText(curHero->type->getSpecialtyNameTranslated()); tacticsButton = std::make_shared(Point(539, 483), AnimationPath::builtin("hsbtns8.def"), std::make_pair(heroscrn[26], heroscrn[31]), 0, EShortcut::HERO_TOGGLE_TACTICS); - tacticsButton->addHoverText(CButton::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]); + tacticsButton->addHoverText(EButtonState::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]); - dismissButton->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->getNameTranslated() % curHero->getClassNameTranslated())); + dismissButton->addHoverText(EButtonState::NORMAL, boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->getNameTranslated() % curHero->getClassNameTranslated())); portraitArea->hoverText = boost::str(boost::format(CGI->generaltexth->allTexts[15]) % curHero->getNameTranslated() % curHero->getClassNameTranslated()); portraitArea->text = curHero->getBiographyTranslated(); portraitImage->setFrame(curHero->getIconIndex()); diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 8c661de75..c6d160817 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -823,11 +823,11 @@ CTownItem::CTownItem(const CGTownInstance * Town) fastTownHall = std::make_shared(Point(69, 31), AnimationPath::builtin("ITMTL.def"), CButton::tooltip(), [&]() { std::make_shared(town)->enterTownHall(); }); fastTownHall->setImageOrder(town->hallLevel(), town->hallLevel(), town->hallLevel(), town->hallLevel()); - fastTownHall->setAnimateLonelyFrame(true); +//TODO: fastTownHall->setAnimateLonelyFrame(true); int imageIndex = town->fortLevel() == CGTownInstance::EFortLevel::NONE ? 3 : town->fortLevel() - 1; fastArmyPurchase = std::make_shared(Point(111, 31), AnimationPath::builtin("itmcl.def"), CButton::tooltip(), [&]() { std::make_shared(town)->enterToTheQuickRecruitmentWindow(); }); fastArmyPurchase->setImageOrder(imageIndex, imageIndex, imageIndex, imageIndex); - fastArmyPurchase->setAnimateLonelyFrame(true); +//TODO: fastArmyPurchase->setAnimateLonelyFrame(true); fastTavern = std::make_shared(Rect(5, 6, 58, 64), [&]() { if(town->builtBuildings.count(BuildingID::TAVERN)) @@ -976,7 +976,7 @@ CHeroItem::CHeroItem(const CGHeroInstance * Hero) std::string overlay = CGI->generaltexth->overview[8+it]; auto button = std::make_shared(Point(364+(int)it*112, 46), AnimationPath::builtin("OVBUTN3"), CButton::tooltip(hover, overlay), 0); - button->addTextOverlay(CGI->generaltexth->allTexts[stringID[it]], FONT_SMALL, Colors::YELLOW); + button->setTextOverlay(CGI->generaltexth->allTexts[stringID[it]], FONT_SMALL, Colors::YELLOW); artButtons->addToggle((int)it, button); } artButtons->addCallback(std::bind(&CTabbedInt::setActive, artsTabs, _1)); diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 71d3d52f6..a9de69bf9 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -481,24 +481,24 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func if(LOCPLINT->cb->getResourceAmount(EGameResID::GOLD) < GameConstants::HERO_GOLD_COST) //not enough gold { - recruit->addHoverText(CButton::NORMAL, CGI->generaltexth->tavernInfo[0]); //Cannot afford a Hero + recruit->addHoverText(EButtonState::NORMAL, CGI->generaltexth->tavernInfo[0]); //Cannot afford a Hero recruit->block(true); } else if(LOCPLINT->cb->howManyHeroes(true) >= CGI->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP)) { //Cannot recruit. You already have %d Heroes. - recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(true))); + recruit->addHoverText(EButtonState::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(true))); recruit->block(true); } else if(LOCPLINT->cb->howManyHeroes(false) >= CGI->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)) { //Cannot recruit. You already have %d Heroes. - recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(false))); + recruit->addHoverText(EButtonState::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(false))); recruit->block(true); } else if(dynamic_cast(TavernObj) && dynamic_cast(TavernObj)->visitingHero) { - recruit->addHoverText(CButton::NORMAL, CGI->generaltexth->tavernInfo[2]); //Cannot recruit. You already have a Hero in this town. + recruit->addHoverText(EButtonState::NORMAL, CGI->generaltexth->tavernInfo[2]); //Cannot recruit. You already have a Hero in this town. recruit->block(true); } else @@ -586,7 +586,7 @@ void CTavernWindow::show(Canvas & to) //Recruit %s the %s if (!recruit->isBlocked()) - recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[3]) % sel->h->getNameTranslated() % sel->h->getClassNameTranslated())); + recruit->addHoverText(EButtonState::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[3]) % sel->h->getNameTranslated() % sel->h->getClassNameTranslated())); } @@ -880,10 +880,10 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, std::bind(moveArtifacts, [this](bool equipped, bool baclpack) -> void {controller.moveArtifacts(false, equipped, baclpack);})); backpackButtonLeft = std::make_shared(Point(325, 518), AnimationPath::builtin("buttons/backpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), std::bind(openBackpack, heroInst[0])); - backpackButtonLeft->addOverlay(std::make_shared(ImagePath::builtin("buttons/backpackButtonIcon"))); + backpackButtonLeft->setOverlay(std::make_shared(ImagePath::builtin("buttons/backpackButtonIcon"))); backpackButtonRight = std::make_shared(Point(419, 518), AnimationPath::builtin("buttons/backpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), std::bind(openBackpack, heroInst[1])); - backpackButtonRight->addOverlay(std::make_shared(ImagePath::builtin("buttons/backpackButtonIcon"))); + backpackButtonRight->setOverlay(std::make_shared(ImagePath::builtin("buttons/backpackButtonIcon"))); auto leftHeroBlock = heroInst[0]->tempOwner != LOCPLINT->cb->getPlayerID(); auto rightHeroBlock = heroInst[1]->tempOwner != LOCPLINT->cb->getPlayerID(); @@ -1355,9 +1355,7 @@ CHillFortWindow::CHillFortWindow(const CGHeroInstance * visitor, const CGObjectI for(int i = 0; i < slotsCount; i++) { - upgrade[i] = std::make_shared(Point(107 + i * 76, 171), AnimationPath(), CButton::tooltip(getTextForSlot(SlotID(i))), [=](){ makeDeal(SlotID(i)); }, vstd::next(EShortcut::SELECT_INDEX_1, i)); - for(auto image : { "APHLF1R.DEF", "APHLF1Y.DEF", "APHLF1G.DEF" }) - upgrade[i]->addImage(AnimationPath::builtin(image)); + upgrade[i] = std::make_shared(Point(107 + i * 76, 171), AnimationPath::builtin("APHLF1R"), CButton::tooltip(getTextForSlot(SlotID(i))), [=](){ makeDeal(SlotID(i)); }, vstd::next(EShortcut::SELECT_INDEX_1, i)); for(int j : {0,1}) { @@ -1366,9 +1364,7 @@ CHillFortWindow::CHillFortWindow(const CGHeroInstance * visitor, const CGObjectI } } - upgradeAll = std::make_shared(Point(30, 231), AnimationPath(), CButton::tooltip(CGI->generaltexth->allTexts[432]), [&](){ makeDeal(SlotID(slotsCount));}, EShortcut::RECRUITMENT_UPGRADE_ALL); - for(auto image : { "APHLF4R.DEF", "APHLF4Y.DEF", "APHLF4G.DEF" }) - upgradeAll->addImage(AnimationPath::builtin(image)); + upgradeAll = std::make_shared(Point(30, 231), AnimationPath::builtin("APHLF4R"), CButton::tooltip(CGI->generaltexth->allTexts[432]), [&](){ makeDeal(SlotID(slotsCount));}, EShortcut::RECRUITMENT_UPGRADE_ALL); quit = std::make_shared(Point(294, 275), AnimationPath::builtin("IOKAY.DEF"), CButton::tooltip(), std::bind(&CHillFortWindow::close, this), EShortcut::GLOBAL_ACCEPT); statusbar = CGStatusBar::create(std::make_shared(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); @@ -1384,6 +1380,9 @@ bool CHillFortWindow::holdsGarrison(const CArmedInstance * army) void CHillFortWindow::updateGarrisons() { + constexpr std::array slotImages = { "APHLF1R.DEF", "APHLF1Y.DEF", "APHLF1G.DEF" }; + constexpr std::array allImages = { "APHLF4R.DEF", "APHLF4Y.DEF", "APHLF4G.DEF" }; + std::array costs;// costs [slot ID] [resource ID] = resource count for upgrade TResources totalSum; // totalSum[resource ID] = value @@ -1404,9 +1403,9 @@ void CHillFortWindow::updateGarrisons() } currState[i] = newState; - upgrade[i]->setIndex(currState[i] == -1 ? 0 : currState[i]); + upgrade[i]->setImage(AnimationPath::builtin(currState[i] == -1 ? slotImages[0] : slotImages[currState[i]])); upgrade[i]->block(currState[i] == -1); - upgrade[i]->addHoverText(CButton::NORMAL, getTextForSlot(SlotID(i))); + upgrade[i]->addHoverText(EButtonState::NORMAL, getTextForSlot(SlotID(i))); } //"Upgrade all" slot @@ -1426,7 +1425,7 @@ void CHillFortWindow::updateGarrisons() } currState[slotsCount] = newState; - upgradeAll->setIndex(newState); + upgradeAll->setImage(AnimationPath::builtin(allImages[newState])); garr->recreateSlots(); From 0051ffa7a90b02acbf2de6e521a86a958529fd53 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 28 Feb 2024 22:24:19 +0200 Subject: [PATCH 173/250] Implemented configurable buttons. Replaced 'animateLonelyFrame' logic. --- .../lobby/selectionTabSortDate.png | Bin .../Sprites/lobby/selectionTabSortDate.json | 7 -- client/lobby/SelectionTab.cpp | 5 +- client/widgets/Buttons.cpp | 92 ++++++++++++++---- client/widgets/Buttons.h | 11 ++- client/windows/CCastleInterface.cpp | 10 +- client/windows/CKingdomInterface.cpp | 12 +-- .../buttons/castleInterfaceQuickAccess.json | 22 +++++ .../widgets/buttons/selectionTabSortDate.json | 22 +++++ 9 files changed, 140 insertions(+), 41 deletions(-) rename Mods/vcmi/{Sprites => Data}/lobby/selectionTabSortDate.png (100%) delete mode 100644 Mods/vcmi/Sprites/lobby/selectionTabSortDate.json create mode 100644 config/widgets/buttons/castleInterfaceQuickAccess.json create mode 100644 config/widgets/buttons/selectionTabSortDate.json diff --git a/Mods/vcmi/Sprites/lobby/selectionTabSortDate.png b/Mods/vcmi/Data/lobby/selectionTabSortDate.png similarity index 100% rename from Mods/vcmi/Sprites/lobby/selectionTabSortDate.png rename to Mods/vcmi/Data/lobby/selectionTabSortDate.png diff --git a/Mods/vcmi/Sprites/lobby/selectionTabSortDate.json b/Mods/vcmi/Sprites/lobby/selectionTabSortDate.json deleted file mode 100644 index 9022e4244..000000000 --- a/Mods/vcmi/Sprites/lobby/selectionTabSortDate.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "basepath" : "lobby/", - "images" : - [ - { "frame" : 0, "file" : "selectionTabSortDate.png"} - ] -} diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index f0fd39701..73e7aca83 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -216,8 +216,9 @@ SelectionTab::SelectionTab(ESelectionScreen Type) if(enableUiEnhancements) { - buttonsSortBy.push_back(std::make_shared(Point(371, 85), AnimationPath::builtin("lobby/selectionTabSortDate"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.sortDate")), std::bind(&SelectionTab::sortBy, this, ESortBy::_changeDate))); -//TODO: buttonsSortBy.back()->setAnimateLonelyFrame(true); + auto sortByDate = std::make_shared(Point(371, 85), AnimationPath::builtin("selectionTabSortDate"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.sortDate")), std::bind(&SelectionTab::sortBy, this, ESortBy::_changeDate)); + sortByDate->setOverlay(std::make_shared(ImagePath::builtin("lobby/selectionTabSortDate"))); + buttonsSortBy.push_back(sortByDate); } iconsMapFormats = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCSELC.DEF")); diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp index d31ce2d9e..a3c128bb2 100644 --- a/client/widgets/Buttons.cpp +++ b/client/widgets/Buttons.cpp @@ -22,6 +22,7 @@ #include "../gui/CGuiHandler.h" #include "../gui/MouseButton.h" #include "../gui/Shortcut.h" +#include "../gui/InterfaceObjectConfigurable.h" #include "../windows/InfoWindows.h" #include "../render/CAnimation.h" #include "../render/Canvas.h" @@ -29,6 +30,7 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/CGeneralTextHandler.h" +#include "../../lib/filesystem/Filesystem.h" void ButtonBase::update() { @@ -42,15 +44,14 @@ void ButtonBase::update() overlay->moveTo(targetPos); } - int newPos = stateToIndex[int(state)]; - if (newPos < 0) - newPos = 0; - - // checkbox - has only have two frames: normal and pressed/highlighted - // hero movement speed buttons: only three frames: normal, pressed and blocked/highlighted - if (state == EButtonState::HIGHLIGHTED && image->size() < 4) - newPos = (int)image->size()-1; - image->setFrame(newPos); + if (image) + { + // checkbox - has only have two frames: normal and pressed/highlighted + // hero movement speed buttons: only three frames: normal, pressed and blocked/highlighted + if (state == EButtonState::HIGHLIGHTED && image->size() < 4) + image->setFrame(image->size()-1); + image->setFrame(stateToIndex[vstd::to_underlying(state)]); + } if (isActive()) redraw(); @@ -89,6 +90,7 @@ void ButtonBase::setImage(const AnimationPath & defName, bool playerColoredButto { OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE); + configurable.reset(); image = std::make_shared(defName, vstd::to_underlying(getState())); pos = image->pos; @@ -96,6 +98,41 @@ void ButtonBase::setImage(const AnimationPath & defName, bool playerColoredButto image->playerColored(LOCPLINT->playerID); } +const JsonNode & ButtonBase::getCurrentConfig() const +{ + if (!config) + throw std::runtime_error("No config found in button!"); + + static constexpr std::array stateToConfig = { + "normal", + "pressed", + "blocked", + "highlighted" + }; + + std::string key = stateToConfig[vstd::to_underlying(getState())]; + const JsonNode & value = (*config)[key]; + + if (value.isNull()) + throw std::runtime_error("No config found in button for state " + key + "!"); + + return value; +} + +void ButtonBase::setConfigurable(const JsonPath & jsonName, bool playerColoredButton) +{ + OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE); + + config = std::make_unique(jsonName); + + image.reset(); + configurable = std::make_shared(getCurrentConfig()); + pos = configurable->pos; + + if (playerColoredButton) + image->playerColored(LOCPLINT->playerID); +} + void CButton::addHoverText(EButtonState state, std::string text) { hoverTexts[vstd::to_underlying(state)] = text; @@ -110,15 +147,24 @@ void ButtonBase::setImageOrder(int state1, int state2, int state3, int state4) update(); } -//TODO: -//void CButton::setAnimateLonelyFrame(bool agreement) -//{ -// animateLonelyFrame = agreement; -//} - void ButtonBase::setStateImpl(EButtonState newState) { state = newState; + + if (configurable) + { + OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE); + configurable = std::make_shared(getCurrentConfig()); + pos = configurable->pos; + + if (overlay) + { + // Force overlay on top + removeChild(overlay.get()); + addChild(overlay.get()); + } + } + update(); } @@ -135,7 +181,7 @@ void CButton::setState(EButtonState newState) setStateImpl(newState); } -EButtonState ButtonBase::getState() +EButtonState ButtonBase::getState() const { return state; } @@ -270,9 +316,21 @@ ButtonBase::ButtonBase(Point position, const AnimationPath & defName, EShortcut pos.x += position.x; pos.y += position.y; - setImage(defName); + JsonPath jsonConfig = defName.toType().addPrefix("CONFIG/WIDGETS/BUTTONS/"); + if (CResourceHandler::get()->existsResource(jsonConfig)) + { + setConfigurable(jsonConfig, playerColoredButton); + return; + } + else + { + setImage(defName, playerColoredButton); + return; + } } +ButtonBase::~ButtonBase() = default; + CButton::CButton(Point position, const AnimationPath &defName, const std::pair &help, CFunctionList Callback, EShortcut key, bool playerColoredButton): ButtonBase(position, defName, key, playerColoredButton), callback(Callback) diff --git a/client/widgets/Buttons.h b/client/widgets/Buttons.h index da14e782b..49d5d6a87 100644 --- a/client/widgets/Buttons.h +++ b/client/widgets/Buttons.h @@ -19,8 +19,7 @@ class Rect; VCMI_LIB_NAMESPACE_END class CAnimImage; -class CLabel; -class CAnimation; +class InterfaceObjectConfigurable; enum class EButtonState { @@ -33,7 +32,9 @@ enum class EButtonState class ButtonBase : public CKeyShortcut { std::shared_ptr image; //image for this button + std::shared_ptr configurable; //image for this button std::shared_ptr overlay;//object-overlay, can be null + std::unique_ptr config; std::array stateToIndex; // mapping of button state to index of frame in animation @@ -41,16 +42,20 @@ class ButtonBase : public CKeyShortcut void update();//to refresh button after image or text change + const JsonNode & getCurrentConfig() const; + protected: ButtonBase(Point position, const AnimationPath & defName, EShortcut key, bool playerColoredButton); + ~ButtonBase(); void setStateImpl(EButtonState state); - EButtonState getState(); + EButtonState getState() const; public: /// Appearance modifiers void setPlayerColor(PlayerColor player); void setImage(const AnimationPath & defName, bool playerColoredButton = false); + void setConfigurable(const JsonPath & jsonName, bool playerColoredButton = false); void setImageOrder(int state1, int state2, int state3, int state4); /// adds overlay on top of button image. Only one overlay can be active at once diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 309b832a9..0ae793b79 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1332,14 +1332,12 @@ void CCastleInterface::recreateIcons() hall = std::make_shared(80, 413, town, true); fort = std::make_shared(122, 413, town, false); - fastTownHall = std::make_shared(Point(80, 413), AnimationPath::builtin("ITMTL.def"), CButton::tooltip(), [&](){ builds->enterTownHall(); }); - fastTownHall->setImageOrder(town->hallLevel(), town->hallLevel(), town->hallLevel(), town->hallLevel()); -//TODO: fastTownHall->setAnimateLonelyFrame(true); + fastTownHall = std::make_shared(Point(80, 413), AnimationPath::builtin("castleInterfaceQuickAccess"), CButton::tooltip(), [&](){ builds->enterTownHall(); }); + fastTownHall->setOverlay(std::make_shared(AnimationPath::builtin("ITMTL"), town->hallLevel())); int imageIndex = town->fortLevel() == CGTownInstance::EFortLevel::NONE ? 3 : town->fortLevel() - 1; - fastArmyPurchase = std::make_shared(Point(122, 413), AnimationPath::builtin("itmcl.def"), CButton::tooltip(), [&](){ builds->enterToTheQuickRecruitmentWindow(); }); - fastArmyPurchase->setImageOrder(imageIndex, imageIndex, imageIndex, imageIndex); -//TODO: fastArmyPurchase->setAnimateLonelyFrame(true); + fastArmyPurchase = std::make_shared(Point(122, 413), AnimationPath::builtin("castleInterfaceQuickAccess"), CButton::tooltip(), [&](){ builds->enterToTheQuickRecruitmentWindow(); }); + fastArmyPurchase->setOverlay(std::make_shared(AnimationPath::builtin("itmcl"), imageIndex)); fastMarket = std::make_shared(Rect(163, 410, 64, 42), [&]() { diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index c6d160817..95be9d91a 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -821,13 +821,13 @@ CTownItem::CTownItem(const CGTownInstance * Town) available.push_back(std::make_shared(Point(48+37*(int)i, 78), town, (int)i, true, false)); } - fastTownHall = std::make_shared(Point(69, 31), AnimationPath::builtin("ITMTL.def"), CButton::tooltip(), [&]() { std::make_shared(town)->enterTownHall(); }); - fastTownHall->setImageOrder(town->hallLevel(), town->hallLevel(), town->hallLevel(), town->hallLevel()); -//TODO: fastTownHall->setAnimateLonelyFrame(true); + fastTownHall = std::make_shared(Point(69, 31), AnimationPath::builtin("castleInterfaceQuickAccessz"), CButton::tooltip(), [&]() { std::make_shared(town)->enterTownHall(); }); + fastTownHall->setOverlay(std::make_shared(AnimationPath::builtin("ITMTL"), town->hallLevel())); + int imageIndex = town->fortLevel() == CGTownInstance::EFortLevel::NONE ? 3 : town->fortLevel() - 1; - fastArmyPurchase = std::make_shared(Point(111, 31), AnimationPath::builtin("itmcl.def"), CButton::tooltip(), [&]() { std::make_shared(town)->enterToTheQuickRecruitmentWindow(); }); - fastArmyPurchase->setImageOrder(imageIndex, imageIndex, imageIndex, imageIndex); -//TODO: fastArmyPurchase->setAnimateLonelyFrame(true); + fastArmyPurchase = std::make_shared(Point(111, 31), AnimationPath::builtin("castleInterfaceQuickAccessz"), CButton::tooltip(), [&]() { std::make_shared(town)->enterToTheQuickRecruitmentWindow(); }); + fastArmyPurchase->setOverlay(std::make_shared(AnimationPath::builtin("itmcl"), imageIndex)); + fastTavern = std::make_shared(Rect(5, 6, 58, 64), [&]() { if(town->builtBuildings.count(BuildingID::TAVERN)) diff --git a/config/widgets/buttons/castleInterfaceQuickAccess.json b/config/widgets/buttons/castleInterfaceQuickAccess.json new file mode 100644 index 000000000..d5d5f2c5c --- /dev/null +++ b/config/widgets/buttons/castleInterfaceQuickAccess.json @@ -0,0 +1,22 @@ +{ + "normal" : { + "width": 38, + "height": 38, + "items" : [] + }, + "pressed" : { + "width": 38, + "height": 38, + "items" : [] + }, + "blocked" : { + "width": 38, + "height": 38, + "items" : [] + }, + "highlighted" : { + "width": 38, + "height": 38, + "items" : [] + }, +} diff --git a/config/widgets/buttons/selectionTabSortDate.json b/config/widgets/buttons/selectionTabSortDate.json new file mode 100644 index 000000000..b5b3965c3 --- /dev/null +++ b/config/widgets/buttons/selectionTabSortDate.json @@ -0,0 +1,22 @@ +{ + "normal" : { + "width": 18, + "height": 31, + "items" : [] + }, + "pressed" : { + "width": 18, + "height": 31, + "items" : [] + }, + "blocked" : { + "width": 18, + "height": 31, + "items" : [] + }, + "highlighted" : { + "width": 18, + "height": 31, + "items" : [] + }, +} From e89835779bdc95f2da44f7b2f5fd9144bcababe6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Feb 2024 13:15:15 +0200 Subject: [PATCH 174/250] Replaced custom buttons in options window with runtime-generated ones --- .../Sprites/settingsWindow/button190.json | 10 -- .../settingsWindow/button190Normal.png | Bin 3571 -> 0 bytes .../button190NormalSelected.png | Bin 3900 -> 0 bytes .../settingsWindow/button190Pressed.png | Bin 3490 -> 0 bytes .../button190PressedSelected.png | Bin 3972 -> 0 bytes .../vcmi/Sprites/settingsWindow/button32.json | 10 -- .../Sprites/settingsWindow/button32Normal.png | Bin 7393 -> 0 bytes .../settingsWindow/button32NormalSelected.png | Bin 7737 -> 0 bytes .../settingsWindow/button32Pressed.png | Bin 7450 -> 0 bytes .../button32PressedSelected.png | Bin 7670 -> 0 bytes .../vcmi/Sprites/settingsWindow/button46.json | 10 -- .../Sprites/settingsWindow/button46Normal.png | Bin 1140 -> 0 bytes .../settingsWindow/button46NormalSelected.png | Bin 1294 -> 0 bytes .../settingsWindow/button46Pressed.png | Bin 1102 -> 0 bytes .../button46PressedSelected.png | Bin 1293 -> 0 bytes .../vcmi/Sprites/settingsWindow/button80.json | 10 -- .../Sprites/settingsWindow/button80Normal.png | Bin 1713 -> 0 bytes .../settingsWindow/button80NormalSelected.png | Bin 1913 -> 0 bytes .../settingsWindow/button80Pressed.png | Bin 1673 -> 0 bytes .../button80PressedSelected.png | Bin 1903 -> 0 bytes .../buttons/settingsWindow/button190.json | 114 ++++++++++++++++++ .../buttons/settingsWindow/button32.json | 114 ++++++++++++++++++ .../buttons/settingsWindow/button46.json | 114 ++++++++++++++++++ .../buttons/settingsWindow/button80.json | 114 ++++++++++++++++++ 24 files changed, 456 insertions(+), 40 deletions(-) delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button190.json delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button190Normal.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button190NormalSelected.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button190Pressed.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button190PressedSelected.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button32.json delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button32Normal.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button32NormalSelected.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button32Pressed.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button32PressedSelected.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button46.json delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button46Normal.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button46NormalSelected.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button46Pressed.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button46PressedSelected.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button80.json delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button80Normal.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button80NormalSelected.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button80Pressed.png delete mode 100644 Mods/vcmi/Sprites/settingsWindow/button80PressedSelected.png create mode 100644 config/widgets/buttons/settingsWindow/button190.json create mode 100644 config/widgets/buttons/settingsWindow/button32.json create mode 100644 config/widgets/buttons/settingsWindow/button46.json create mode 100644 config/widgets/buttons/settingsWindow/button80.json diff --git a/Mods/vcmi/Sprites/settingsWindow/button190.json b/Mods/vcmi/Sprites/settingsWindow/button190.json deleted file mode 100644 index 0af55c417..000000000 --- a/Mods/vcmi/Sprites/settingsWindow/button190.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "basepath" : "settingsWindow/", - "images" : - [ - { "frame" : 0, "file" : "button190Normal.png"}, - { "frame" : 1, "file" : "button190PressedSelected.png"}, - { "frame" : 2, "file" : "button190Pressed.png"}, - { "frame" : 3, "file" : "button190NormalSelected.png"} - ] -} diff --git a/Mods/vcmi/Sprites/settingsWindow/button190Normal.png b/Mods/vcmi/Sprites/settingsWindow/button190Normal.png deleted file mode 100644 index f120bf2946d54c8ed31d7475b785ef2be34217f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3571 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 KMNUMnLSTZ#ga=Ci diff --git a/Mods/vcmi/Sprites/settingsWindow/button190Pressed.png b/Mods/vcmi/Sprites/settingsWindow/button190Pressed.png deleted file mode 100644 index 04821e4ce557c943b154e12031a23be250203fe8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3490 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 diff --git a/Mods/vcmi/Sprites/settingsWindow/button32Pressed.png b/Mods/vcmi/Sprites/settingsWindow/button32Pressed.png deleted file mode 100644 index dcfeb03e1223fe0ba890e684c0021e09b05280e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7450 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 PSFDz1Hm28%T|@r|o@McD diff --git a/Mods/vcmi/Sprites/settingsWindow/button32PressedSelected.png b/Mods/vcmi/Sprites/settingsWindow/button32PressedSelected.png deleted file mode 100644 index 5ed89e78431e150a53614aba52d0b2fe988fd761..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7670 zcmeHKdpwj|+n%CGghG_fpp-J^U>s(09;d+|r%+_fJQ#+VW`;qEluFKWhz_#LDWOP_ zib6S;b51#hC<>J%e9x%eYkyzge!u{716fE5+9l^c-^|rL02ZtA<50#50e5`wBQMz}w zFYD3vb<4wVJa+ZG|Fmu1M`R@K%-pMDw(j%(-T-Y>zqfYp*AXqz(Ax=%zpHm4^@Gbt zFbdtfo;H12OcKPOi=stM&V4C;{$f!kq;(l{e5tbVi)gg_HNkr`@FRgEy1{YJo)3?l zG0=4uo&Q?tjMd&Uc<;;Qq6y5xbbi)5-UG)kf2B_^=^YVj@=pd{g~iO2^|FVZj?Hap z6U*N;#-j$~71ovs6Ae@+_P^!dvgJHg;gCUQ)!y9O#^*QipZGAl$>-n2^fr zfe7ObC)DjejN2*C4-Hn)o$te>=tCK~ZSgq(URx$%YN?vTXzOtXi! zR@wWaQf*Ycnr{$}Mq_|ROfprY-BdBhqc$VK(Q#a*!=`xD(K9davph_XG~wuz(caMoKa`n1(C5Z~~mvOz6e$x!sB4VU{m96xvKrr&o3xz2_49v&J!JEdJsSUPn{`OT7a z4d&$3c1HQ|XP!2@a@KaZj$K%l$mz%gX}+?8jGVOnvmQqklad|;6$o6hYA#&gDH&Z@ z+F*T<7VyrttgSV9FQRWSvo=|%>jA1KuOPOrj%3#bcvdyO*NbsAYQEenMGR2k(LH*K zB={|o>pZKvNtYp*nx#8Ny|s5!cAL~3J-g2KVaHq|T1z#>v+TzFep~dgmq zyA$wTp$~T-;Hz>GR#fRyczMo(}Ptwn6X)P*kPw0QSY~$|sG+kW*#1GwyjaP6sPLj zG1ugWK^@$oLf^!*Q>#~KTeH8qIkjn>dYd8FsQxK803?v`zB7JbU87u z@q0Hxs<@Q_Lf{qW7KKr#Jxv-r`ghMn2A4{RAD~U2Ae^Oq*Re&M27+oL5gd@LK#@>bgO{;1jt*ERW8B3NGH_FUn2pv*mxA z?H#;-*rTT{?SX;JlTK>;mQ5X=wfGLj&&IjVntBO_cE;No>2iqq#&X?H0 z$~8qdZ!o!ApPrtWLV=X5A4VPSB&!>7UgiKhL{`%>)5E`@_S16yJm z?`Y&2y!A-pb1WhQid-!;QKC=&_Ih?iTC2fI>>in0eD_cgt|o!ID>>p~*_RU{z83eM zTr)2Ut*iTRb#tU+K*zPKR>y9LhY|L)irhrNkDGDTHWKOLG4ccc{3p#?x%kaOqn>8i z!8ZI}E}!Zk*jP~~BoHwE`?~5YX)Pj|zxz}hWW{k0ira)(&(^n`=Q%6~Gi44Q`Pjuo z?wK0@9HVzA?WOm2?S@v1cTe8u4L@#jx!Q^{&G1}uavtuIb#4l_zU6Sj&f=~^cSy!G zPML=y+K{@)Q0VIh>RNjNWKO~SXEl}udi79m;bg|=fL@mm z^M<(}2j{V8viNEJm(Q2eePTa0)x@QGpS-#*ch(!1{pFj#anTvy$StBG4}5j&cU|le zzrLWlP1S^VY7N}JbwQ`aZfEhujX`&W5gDFS!=gd*7suPggp`P6wfrNVeY^d=Ot^L4 zh0jM`2rhYBP_}UJx8o(I+xI{|;I75LTl3t$O(6(=+}x#5xl=EL7?*BLRy?g( z49nRgx%2Kf*Um+ogQrWjo3B6Lm@_YW+fGpO+`2WLvZ+C@baOqHdHFnyyH2-N-i-ac z_VdN9tk_bCJ_uw@7zKy3#N%*(Y=yu)myX408dvIwpYG2zG1#+d5;_!{j&%?cZ6-M0 z<`pwJm~P8+-gNmY9v43}w7W!1@|(j=Y>E2YiGe-5q1w=VCG&GiRzqxjxclm61>9-r={$XiuyiB1TZWFZ)u3}e)-%4pc>Nf~TV)vzy zbQCV|e+aLdHB(a@5*SJ$n2tTLkaPp^GFrQ&)ZTNSm+JY(U`?LIP2F>^;rwEE68SqRQP2}Rcf^JAO8DAXhK22( z+@>Jk!jhW(Ah>Tyq?F9Ct*E&jvdzXgTKb#d@wpB~bHaW6WAPUYu07J%bX>N2s8AOc zUfw;pD`Z)sqy@X=xaGp)j)%?7vPKmHs`Mj$&95~F7(C*EEjw|H9on#Td+t}y-&~y0 zc_a4bFms_p>)AM0Y=$$eAHs8{!alsPQ}b0{XWzYGukOW#HKf~&^drzOpaleNDd67J zo?wP0(Y!$;(_8^1e{VYI4G2U-+n-J(c>ye_E8tG?(Ug5(RxS&rkTqows1OhYIu7ul z7zZ){>p*iGQlJ+JLzdOn;?wZQf&|_GiwO1irus0k{+hBYyjbv>(+!t}u0U8`nzHr; zODK-U0HA0kv=Rbl;7{>G$!hUIH5g0jy3%}EnzFLsedrHzj@=FX z!OkBEIPDdBCW{QmgAFt|9}oaXArR^?1PX@2z*onEcL{`_(LT%{DuQ~#{fTrqQV9X~ z_Wn}@lV#xduYA9ZVA_C31h^Hzr1>&PfPo+2!;)K_luq?!uFmPp1UOwQA~@*PNHXasj_%8#u3*R{I6wuw!GSPAX5^pnVEoU3UruL* zd_^d%K8@tdVZrNb%5nyPCDTY0GIr%sO`V8TRzVP9L=_cR7#cxD!CYO{kuWkE0MIBj zNsUNEuA;*GFj+(&62PGX$(1M|kGdKXO(LPyVMH}oH5giz2$G@I-C%A+S3m_oliY|X z#3~941_iV>k-B)7Z?FyenWEiZ@`*B64Iys;a4}t0O=!qgBA}UJA9?70skxT|6sEAps{`aZ#@5k{$$Z*u-G() zmj%PZ)e|7G{+j1k;GaxZ;4;NzF#_=aW>WtRr}5pG#$YUs5wI%1HE`s6>-$n>vh)KqFvi zb&N8MNW>6fs%}I#H+5B2S2fq~p7_bmq`9%!LC_3P$$((}rrmIZNXov(?}%9s~k*wH_Lr#RZG!7|z<4PNz}; z#-DZi!#Dp6ZdLpbkN&&x)v)i;I2t_wtX2<}1>5H@)BghaoxzMk0(_XXzZ&{#$ah&* zy+Ob+fA0g|OyG+S{_{=u!zG+T`VW47xbHuh0i^yX$=}lVAG!XK>u)LWx4{3n>mRxP zmI8kZ{Exf--{j)^*I60x0r!1u@FZ+iPO=40(d%7Jjr3Q}(~x;wSul9Dfo|-;gg}(! zIKMUi8QOkeCl3oxFyI;E;u1yfTi(}`1A%az#_Q|a7(Yu)(#@w<3+r2uJ0|C}%pO8w z9zsmhyenb}_G0xeqqZ*#?@foj-e49dr2G={ZSJcbfT)>Rii)}w#FO{vzHMSkoN(OE z0m0`EeiArd-4r1?8^g8YCr%Mk`0ds#_;yvGaK_isn@^-ihDZm0*&1UJ{4Fgeq~A%Q zE%(uvkLSymCeyx^KPQFs^UFU9oEw2Sr;U}d4?jA#P@A^&MZz+(;%ThjEwgiS>)2O= z@OeYP*Z8%DC_Q*q6pnBD6MOlVM2t*K0+Blx(J-Pl9jkY9h;dLWIb;kccYBSVBwpg+ zNYtaMlFrVEx|@3UR8O^?Y3eSO&Oy~k#H(A46}nt^&N?tXYMJ7&y`cHUr=Eq@#*~B* z%I1^NZ94-lYi%76dX57|87ac&UMgIvPrsZ!wPwoT(CLXJ%6jRnugj$xdY7yAm?-ub z9*dofhtiDqY?!sDG3X-(#rWR!44>QC0u3@-?nt36#7 zb`h0>pvWYq(^Q?62AkjzR~cCYt7G#`RL! zHy&&C@vTDk@lN2@Wd8oU;ju^7%VbdeB3~$;)VpG!=>Ebf1vr{j zO!=C9QV4$Dyx>@-pr)>WH;!+dGJsznAN>)v!R;vSRGlDt@deku>tix_TQcH0V4Bd^ z!gN^>$3B>j<3-jR?a9bW?;MXke$qguiD6=Ur5Z6d<20xMYaY5^MmZ~|vau)ciiid7 z;q*z{J01D06%#={@;h!9Kct^sFn1XgHpwAZtDbuM^+lW02_Iel=W^VwWn(hV$z$Ea zxY_sreRO7_Rc1Ttvj{fX zsE%5~J?$|r!K%&mzg+yzLGkD-mr|vBZ=I6U3?tiS?!NWgT6t0KO3c91>}IZke6s7s z2IJx>qnhT(*8z9pOO>Q+g+Hch1->Oo?0#2f+dCPUq|CaWU+*nibeA3NM=687t>K;C z=9!t9P+NbeYN2R{Z;hu|yLU%M8`INP5Y?}5y7Ae)2;E`NqIW>K*%hl}Ja4{j8rSv6 zED%{<`ux6JyZzJQxwU<}rw+qk61=q*M`pEyXBT5OA1U*qHb;m|UQ=C{BbMxUtlMfX fFD~Y2S#3E)(#*xkXtqR(Q^R-zbNzfhm(c$LL?3TO diff --git a/Mods/vcmi/Sprites/settingsWindow/button46.json b/Mods/vcmi/Sprites/settingsWindow/button46.json deleted file mode 100644 index 1d6ade89f..000000000 --- a/Mods/vcmi/Sprites/settingsWindow/button46.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "basepath" : "settingsWindow/", - "images" : - [ - { "frame" : 0, "file" : "button46Normal.png"}, - { "frame" : 1, "file" : "button46PressedSelected.png"}, - { "frame" : 2, "file" : "button46Pressed.png"}, - { "frame" : 3, "file" : "button46NormalSelected.png"} - ] -} diff --git a/Mods/vcmi/Sprites/settingsWindow/button46Normal.png b/Mods/vcmi/Sprites/settingsWindow/button46Normal.png deleted file mode 100644 index e90e6b820f524b46cb6f18bc71725f2e5f0f3ad2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1140 zcmV-)1dIELP)+=cRf5Zke zh?i0VFB2)C8zv-}&;-H&0xr`4zCZ(!5CDM}P&xz-paj%kehFuFDhu$e7xn5ViS&V4sC%)Cy(aL10Hn)3aO_kyjQqt;!CW+DJtG8|2 zqqDi!iq+ek*ZJi!YF=n~w3tBVx-g__`o(s(A(g(+A5>fFMHjUd>@XeU>(A$M6fTpx*R7j{W;|C|oCh^q z79nLiwv{3I%T*RHR)s~5%Oqr*D6dr52bF&fXegIi0dnJ^P+!XAh|q4>p;;q*&G?{* zXc;(pYRS5-fpur@=W`x%K=fx1eoQ}(_~i^h?blniQ&b{dedxYCjn}C|gI3O)XFWn> zDFgbyXGX9TQw#O%le*|A%GNBgeKc(k+7V?Yk4}~Qmr0vX9$=%lcmSWx&vXj;t7 z7wPC!X>EGv8Ch~NGmmLlq6&f$ajvd}S~FJPsb*|jL*2FslcJ^8u?P;NYm79T`>60R z6}?mc_J=3oS&@oih*|f0+e_(Dyqx4H%Tq!j^UM#5pqS`)gcgwkObi^1Fh+g{H2!>rDr>ji*uWwK4Kcv4)5T_m7{xs& zP-PzO+K+sQwe3zBj-}|j%c=%1k4bP8w2P|cJ3Pl@&g&CRngS+(&giP0HZeypwZ+r+ev*Y98Fcp|LEFo>IC;waOC?FsfXift>^cT;@4ey>0GLoTjSnO95LVu?8jrN(7Wq(|_xz2DvRX*F< zqlji?Y;wEC<8r>bRS!ccu#A41M6muw{`)`p|Dg@ycko{++7#5&g9RG^0000IO4;;F8xaYH;EMLZoxJsw6p z9zr`ENj)A+J|0Lt9!fqQOFkY%JRLwf9YZ@E3~ws9000BoNklzJJ-hUKVw)=rA=+l!Pe^U`5Tjh4B_bJkBbaN7eC7izqBw8VQJM z#3flwz*-Hg3MrB^!Q*uOw0r%1dNv9%mO@IST3|v}05HoDUe(mGb}V0?>=OfOeU)Nf zD_oPqVljbHVn{rx7HQkM44A+66PXCmq2z9*r$dRVF{vRDXGh4{1a&O)W%pyhh&v^N zCIUq=92uIG?{^et>5a(z-mCxo`^WC{e#v60nnB{dd9I1ai)D8y#T+g~xdOKAKRo3u zRdWf}xBI%Tw~bJ|sivsFFzu_u5p$WYue%TXx6(Wo4i0y~-HUQWjy7z(4Tma#^7&=D ze%rn8mtd0l6te>LUeS|UDJjO30atfWHg(AfGq^CbW z1{MD~&SS_$g-#jX;c%eUq9bPA+(kJY=HXFAsV?6??><}>o>L^%48VKqwMG@J+g;4g z^D)Hd0vfq41Lpm*%zw;EDh@IA=J!qY*85|-NeQf)``R6v2K!S!&77#1R2S%NTmO07 z)zP=wbmRh70aZt|INP>r#A%!Tx9R(FzDdS_d`2VK%r=`@ z5M)^pG%W8*vcNh+?PuPb3cp_*$#O}3j0alN&9&Hkzs{jB107*qoM6N<$ Ef`ZaTmH+?% diff --git a/Mods/vcmi/Sprites/settingsWindow/button46Pressed.png b/Mods/vcmi/Sprites/settingsWindow/button46Pressed.png deleted file mode 100644 index edc1813050291db09d64e476bba85a7326b1cf58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1102 zcmV-U1hM;xP)FxwJa(4+0S={{eKW(6G85~AfJyt)RAWAynuCWX_ahnnoAV%Z zR#i*k)HGcL#0&S&Nr(>d0C=)bt4xF>(NB6%YMe0_8jmuvuDr0WpbF*QUw&>&F74ycGel8x!%E*fw&GFFP;OG%+ny_Dgp4a4 zQ^?99gP5#gMFKIHj7?SNst1~YIFDJVc~}gJh%yxGOakXQy=_AXqKhKWqF~g#qVHL* zVagDTxG=0^g5;PC4T6A9Zzu8fH3DU+i&0vf_HI?a8;^HSF9U4gF6X)j0O_nPVQq84_p*b zB8Yb`x@)RfwkT_nT&(zNpfPMII^?4K8G?^*x}V~zF;18Vy!G(#nrh1gA~_M)soZIP z{oxS&X3PsuLKBDZJU8W{#AC()GW9ZpYw;^FBO?Q#$D9=jiC?v9EM)TQ;w*bPWXp30E*A#t=&nON9z;DJMm-)yJsv_l9!EYNNE0t?A{y&g_Y-Ou~PT-n`9 zHPdZSIUFDd@No_GRL(m()OdKCWuFT%^izJ2;UpJlCR*`)?+X(F&Mb)I(V7IPzN$ZyXNukta+ z-Q(_1pa9LgDF@9*z}9vX03I6@a_uf;lJ@QGKvY4v7NmOo;m2?H^Ra^CoTOlbYRVYu za(egheoG~yMzu-^;dlFO5b0uIP`m=bs;n1V?0@_7(*bJZAXPIC*iVIuhIL8<0u<~T z+W9(8xyU!qr-l`?Jb1Y-9j5(MA1gJ2%%;aYf|(2nNYnmP;$c`-Kq*`t+hakP2`SZr zQYSc0Ks^W&KPE0_-G{5FRH_VA06?M>Ek#sH!Q&)M@N|CokT^>yYRw6Oq6Dof)`+!h zfJks-B67n#f}h{Ke%gu(VrwLnX$8nhBEnS(-MXllufsF-!^hlvxae59srBwA)pA-> zcBKlzIEyr1ZfoxS_RIN+lsSBakeRko_YFm`ih3m=^Fm2@*G#?rdOookRSl`+grsqV z5E8iGy#er8lD7Nk_V%|2`~7?(RuQ2fwy_xX3dE{p#;vKjZKd5~v;Xnu`2^gBTG4U2 zuj_ifQ8ZgbDrRp=$7*hY)Sg~Hy#I7QwZ*VOu`u)etO=kKYHDmrL+1#A!{dg3KA(y* z!Hgu~{oZ|KWNQ_%QiMr52%%Jy=~!^vfFNx6 zm(TA`(>IflusN&PV)vWKb&QwWm0JN38tbr-N{QQsL#3wNjYF=N_3y8DF(0?SNY0JW z9>HrLvf-bezn^W02F1!|!)$r^>U|k6gQ*;%w?mATdn{XO&x@qHi!UrosN1q${`uE% zKJcpQ!+OsNmvyYJX@CFm_4dm={a1r@6aWZ-Ks!hY3ixq9W#j7K#4X$g#8+{;0p-6U z-tn?anxYzsMahUgT-YUNS;7})TWwR#E3LE}6Eo`xSCOZtZ!P&f00000NkvXXu0mjf D{dYCI diff --git a/Mods/vcmi/Sprites/settingsWindow/button80.json b/Mods/vcmi/Sprites/settingsWindow/button80.json deleted file mode 100644 index 50cb97a40..000000000 --- a/Mods/vcmi/Sprites/settingsWindow/button80.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "basepath" : "settingsWindow/", - "images" : - [ - { "frame" : 0, "file" : "button80Normal.png"}, - { "frame" : 1, "file" : "button80PressedSelected.png"}, - { "frame" : 2, "file" : "button80Pressed.png"}, - { "frame" : 3, "file" : "button80NormalSelected.png"} - ] -} diff --git a/Mods/vcmi/Sprites/settingsWindow/button80Normal.png b/Mods/vcmi/Sprites/settingsWindow/button80Normal.png deleted file mode 100644 index 6ca45a17650a33817bd479524aed29c9e001a7ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1713 zcmV;i22S~jP);>4+J6*1tSp!9u5Q{5Ctg|1}YT>CldxF5d{nd0wod!CKClO z7zZpC2Q3!|D-{Mc8woBK2QnE56bS=190@QO2y0_tO+-L58VEER2ss@Ju)t@h000G` zNkl(7fum=GYX>hFkd25Q?*#gd%@L@h5s^b_q5o1QUG<jmy12>73(@TWSdLH+H>}`iIx4GZr?05KSOm}&dXgkSMTV_>JSxS7$fCr4Gu9U%i zkq{s2zt8WU|JUvGbS>dmDTwczH!-nVO2Lj@-VT!Qzer$j z{Pkxou$!!#>zE@r(+1Qwf*Th5BIln)- zxitf`_7Vg>gUZ`gmylc{JK>X>8JVTb^ z2H5#7sB49ojC(Y!(OEW^eU-^q$lqyFlQkEKly_aUiv+}kchYVAB|)3JF#WwOT}gNP zWMMz>Bk!m`iZTTW(^Pl=NxOXTV@0`0RQ{D@vxQqn1V+1*o?Dg#X_^1Dv-A))@!*mm z&RWN*OU-`7Kh5ziqTWY)pqFj?{>}0mrE0cd7WAOQWSLN70otpU@*?fgbW<-W6WO9n zqFtBHMcE>m4?amZ(L}x|;uJ@YN&ATV zi|dz^ijdx__D8-*xIM>SId_^bjr6`1j0!k0@1YSw46TK7+hLq8uonJu@O&$3&McZ( zqDT8HDd%-6s9O3smVqA94ehHG+PKqDe&z$ISre3NHX&KQTxwbW^wz)eKRHH`BPrxZ z0P=^wpHzf>+mz0*{doO2hFaeuzKJ|bC$F?RkWFaLT#&RXSW-AwT`{5-L(^R`N}|Cj>>^!9kRU~J|Ss3shJPvdlZ>ln^{g>VBjJt<=l-$b8(#e zSz|G=7p1+YIo0K>t)1?qd#v&*woCHOVO0bi2M6cgYRv0#-y~*F(oHk(^K2Sq`n(NM zT;^8dy7Qz^;v4Z=dmWKw6w;NM#Mw4UD3l&_6&LwILjONecGukE_Y7a&Nqq7ek1U?j z1ib_i{M-9B=&8$k1d~c;;LxjN(s6C_@c1IJzkR-{2Bwe_l+nrbZ-{Qrmdu&X~D5A(Rh$AAIKg`qJk)^^skc_!4Odh7ECN z);jZueO3n^#6G`BsC>-JLo4kB#mUw&Oq1&d?zTXr@lb2#PYgAsN z51~+dPNI=`gvr#I%?o#=G@Y4F&YGIE*!t#!c&#rI(SL1yyNFDONHL^HS`K8F)F(^E z>MeMAvkhJfB&Ni3(ypz;Y^j6OOB5I%bJs~B+@5(;_m2TIA4|T1T00000NkvXX Hu0mjfA=VG( diff --git a/Mods/vcmi/Sprites/settingsWindow/button80NormalSelected.png b/Mods/vcmi/Sprites/settingsWindow/button80NormalSelected.png deleted file mode 100644 index c34047b52a2f4cd0aa0db99303354047b39a1962..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1913 zcmV-<2Zs2GP);>4+J6*1tSp!9u5Q{5Ctg|1}YT> zCldxF5d{nd0wod!CKCm#s&H^xH7^(kEEWeX7Y8d925eb0H5&;o7Y8yK2&}4b6bS=1 z90@QO2y9q1Z(22NSv2J0sdHO3t*UTwTQzZ7HF8@ut*UV|8VIkeaduobuc~o%TsAZs z2<76b=HjVCJRLVWjEbah!P^hed>@p{Q{|snb$t0;H{Orh#et_%u*KMmHm>p>lbC zc`RHCmU6uw3q}AFDTt(lmVpIK6BZ#+wRBcx_}Xr5+uOYfDcqZTH}1Z#dU<|z`t;^Z z%Q7$5V_6wUxo`v|u%w|pjRm!#EZJ*cn=xB@yM5lid|mhS3^vu8U0~>n%ePkt!_(9E z@8kI4J0fW$F&3&3xRT7%p&!w_nN{xhd-Gn??_YoX?CE>&&IcAdOBo4i{Qtsp8HaHp z5NsZ&gX~Rm$HV zKV0V`aGpUHTp=wxdwNK?Cqr%Bwp%j|PVX5a2u6)&a{2cA^zO}RF|(>7;h{WUA7m}J zh6Zv0uxHPoZEMYwLoKTB#})VH7Ja)n53LL+aDGUBJ};2kT{Bzjj_X89>1|aLVSso| z&$Zv~O@R!i;_1o~wr1n*F$=C^VGlXK`F;BH?NV7dd-cBEwiVNLl0md8NK6MQ%WRDm zn(5YanuaS%^9sN-=BSv5VarAz|ut)@NYdk{v^z9O@t$z263{mM&0ua8MZ4?e_|MNu3 zT(gQ4WN-UlN6MexpG6qNT&soxL{PW?D5wry$Bh{H&UYy3Jrca$HXMRPyh}iSiLUqZ z|DP0VdmXj`l_bq-B1BEW87!%|lR{9_^7FZ-pPQ0Zl}tIS?XM0W-g!u>#q@3vAvA-Q zx;d@nMo(we?qi=+O4fV0jcq;u-BNh_Yw@myMxi)Qcfi+go)kL-2ekm!Bmdl!YRYQN zrB!gL)tq9=7%Ul}0ovNC;rTBubx9uKw0e4c`|$S7$$^rMn#*C8@vla$+P00oHIGq> zVGF_aHb_>r$~iUFye8A-+>BIA(&Y5`?%|&|r|PDvmBkSQRdTCtRsrwbKzJ!tNF%x@ zH4M>eq-!t4F-R?pah2_KC-xn%xoGW+#lFosuX|y~P|Zon5X1z*RQs~5M~ob927sz< z;4p@YZNp@ozJK@d?9J0Yka}A{fCSLBCDvrk1>-J5oYZ#tvn`h-G3l*?G*>o_C=@V+ zBTT5vZy}DMg%JV^lJ(Q^31%uO43? zCRc*XMV-V5k0_O$ETxx3w|3U^zke_4m_hQ&R9xW9=H>vkogpBC&R1{VAAqi2&h&O7YWtsxvC;%H;-0_%WzXgtr6H>eRzKde6qG&y1Brq*L{AxZ3@ONL))536Vj)I z0%V~TlBre_-Zmj{&|$&+xNlECeSUcM{gX37jg`}Z0LJY`8F34*%Z1V}jOoIGV70Dl zriU>!)_l8~1#8*qxCLK*`FbRWx=}e?2aOnW|L+^p`Qp=z0g*LxpD}5N3GM}AaYO+u zbzZ|Kpi(uq8&7ZE9f1F{SQS?Q!yxnpw&L|1L0X9HKT7=q-%e59;fD|qfv6OsO79Sq z0Ev)NdcbSmxi|LofI>b4OJz;j zI*&Wd!K{0SrAYNz)(Oze diff --git a/Mods/vcmi/Sprites/settingsWindow/button80Pressed.png b/Mods/vcmi/Sprites/settingsWindow/button80Pressed.png deleted file mode 100644 index 13758a6f4a73d77c7d36adc101ff257e0ea91b66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1673 zcmV;426p+0P)*$2?H7n104+m9SsB-3j-Vs10oOv zBoPH64+I_#1PKHJ9}WZ{4+J9+1t=2*B@zWD5(Om@1uYf^ClduK6b29m0xlN@DHH}P z6$Wf*XG=##EENVZ7zfJP5MlrT1(r!fK~#7Ft&s%+R91R(Ij<+c9z!`H4Lr2mutw?g9h zrj!t`9ux>7Fqr@ZcQGadAV!4OE)WDD6F3(Fy;$sy(koG~AaZMO2!qbD(Hh6|c4_Li z!4lC#l-cm+&%XIlTrM>1+C%>;AbGRljr#O5?t0Igp=>kK4Qv0LDTi<}c21!?R*I#9 zoc&O_qth+#tmb9mEvh@Nm%`1mdm8(;T^D4nvy7X2qPZugD<9eG%4VT8GO4Is z3f`r|CK3&u!JYXOFsJ#m;}Q1rmUiQ%+3n03{%P@|MC3riQ_mRiZjeG9>;b|rX#H!b+ zocW7@^w_wv?%6w(=Y02Z)Rst%RsVP0}L+>Bm%eB=5Ew8lN%fmb6g4mo{(44j;GYM4|V0)CD zpQO#JE^0YsAgbpGJ*Ca;F}HjX)7Bu^JWr8`gLl$J z^+G(!eHTOYL2I+?!Q~;jz@^oq?G{e~$u10`}k%hXwkvVorxsEv8QK_Y~?w9x%pEde|z)Ha11duireIJ zYC>2HnE158-UdBc%Nyh^(YaVCH(O8-R-ZzJX=Sg8Z+{a=x-ki!x@U)PKtXp3(bwmZ z60I}$AAO1=XUfx!eRwfRvitZ-UO0zz@iZzA>>kB8om zE=M#lY>L!y26r)IPC9b-wbu?iSgbz;obKZ^*Fw!fUL{}XNkht7jB|L>cR^{M%F9>` z6E;$q0di@%Lc?by=WAqMPX%H2hI@wyG-&a<>1Xf;(&>F}31b93d2gQf2A3xn=x08H z(mJ`!pz6TH8$9m;2C(ohbzx9~fvQy7(JsVCviRDgp6$xj-<7-~V(k$z!I!O7Nhe)| zDWgg3?;#K{6~66I>ylkqd(098m6TU+sfM?-sdti5`^+Jc8J>%VtnP0Dl7`LiMGCC5 z{6oN8%W;wy37NErxrX6+b+Bo=lTnX2B5T*4)iO7PY-vOo8A~K*@^mfE#A7}Mtnccz zOjNr;R5lN(D@8{%3Ef!2=T)7jCZ6D_Kwya9N8UYg^S7oYXFZi&J`>W0?~;E4=6bvX T$s6AO00000NkvXXu0mjf#CP+Q diff --git a/Mods/vcmi/Sprites/settingsWindow/button80PressedSelected.png b/Mods/vcmi/Sprites/settingsWindow/button80PressedSelected.png deleted file mode 100644 index 47cbb044760c4c7a97fc84cb6ad3c1171151336e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1903 zcmV-#2ax!QP)7YhR`6$Ts*1RV|p>gcYquXJ!*HgsJ#b6q!aTsIyM1ae$Aa9lQVTs9&Q z1S1gzEfxl{uXHXK2J7gqLp&ZtJsw6q9z{JKLOdQvJ|0RxA4onQNj@J&J|00l9z#7I zKs+654+J#;00tjPL_t(|0bGg$0z@$kMC&&FwvGEAaf|hfGlLK&lyO3dz)~tu8nhOe z^MHf`B2W;;8WKp20FXkK7iCdZMP1ib(bSN&br&Ux@qvhqL!o}KjKde>__dlGr)hdG zAM822R};>F(7gP!uD<+g(u6;@&7{Zf2GiWNE}k95$3753gY#naq*BH*RN}@sO>ii6 z6e%GeB$kJIJWdP<3 zF!iQq-8Ze86R3CXE@1JkVF1@RAH7cVyw1k~TTW@1;Rwn>vw@cD!$L)AnN5l`1v9Mc z`QQ8HzHR0f44{}~0tsjU|MT&GUtizc91ic?P*^&o49>%T&@tt8It+iIU4g<yg9Vn1HtLX9a@I z#Uf`4rV@%gfX^>Q0Z@>0cz|CWU(CXGC8gAdJZ&cSpu6h81rVig+t3j02-&N8pfC}L z-d6eUcBWj!VWuih$0;G1Ys1iBqQuSInw+bG$b!@2*11L(@x zf(+iHb2%lZ6^zUj9C@N;A~XlZ-ogs=$Vs4^9Qex^eLvA@&Jnz~7$GkM7^Na$x8#1}@8c}5b za`W((W0c!EP3tS-Dzqdi0k__~i4TWge*f()OvHQjfU1?6kA)*RhxH7C_5=)iv+%li zWK}oSAey2|UIIugwZ7=T0Dt)QT~S3gkBkn6`9dBDx?Fl8lDY$?m)i9FxO?viL*KSw z!4QG zuI_s);q9_Ls7g>ePD7)2LbS`55+eFTs_*j_ub*}*Qa-<>nRYTT`Y9h%a;-7 zS|7hWOGy-gx>fbaXf*;yDE2lnaemnTxW9z<`eoZ= zcz3tvF594~y2B0KI|JTTXw*>ENLteEk1sFZ{CTCn+`V44hBVW58kyYPwZuzVm|6ZSefC>jvPDEmM@M~ pAlR}G9cTo~Ax)ITRHQ~>cn3*;{>IRAY)AkA002ovPDHLkV1g;=UgH1& diff --git a/config/widgets/buttons/settingsWindow/button190.json b/config/widgets/buttons/settingsWindow/button190.json new file mode 100644 index 000000000..de1e0f1ed --- /dev/null +++ b/config/widgets/buttons/settingsWindow/button190.json @@ -0,0 +1,114 @@ +{ + "normal" : { + "width": 190, + "height": 32, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 190, "h": 32} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 190, "h": 32} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + ] + } + ] + }, + "pressed" : { + "width": 190, + "height": 32, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 1, "y": 1, "w": 189, "h": 31} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 190, "h": 32} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] }, + + { "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 96 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] }, + + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] }, + ] + } + ] + }, + "blocked" : { + "width": 190, + "height": 32, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 190, "h": 32} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 190, "h": 32} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + ] + } + ] + }, + "highlighted" : { + "width": 190, + "height": 32, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 190, "h": 32} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 190, "h": 32} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "rectangle", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 255, 255, 255, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 255, 255, 255, 255 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 255, 255, 255, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + ] + } + ] + }, +} diff --git a/config/widgets/buttons/settingsWindow/button32.json b/config/widgets/buttons/settingsWindow/button32.json new file mode 100644 index 000000000..6d748e7d9 --- /dev/null +++ b/config/widgets/buttons/settingsWindow/button32.json @@ -0,0 +1,114 @@ +{ + "normal" : { + "width": 32, + "height": 24, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 32, "h": 24} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 32, "h": 24} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + ] + } + ] + }, + "pressed" : { + "width": 32, + "height": 24, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 1, "y": 1, "w": 31, "h": 23} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 32, "h": 24} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] }, + + { "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 96 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] }, + + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] }, + ] + } + ] + }, + "blocked" : { + "width": 32, + "height": 24, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 32, "h": 24} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 32, "h": 24} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + ] + } + ] + }, + "highlighted" : { + "width": 32, + "height": 24, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 32, "h": 24} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 32, "h": 24} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "rectangle", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 255, 255, 255, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 255, 255, 255, 255 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 255, 255, 255, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + ] + } + ] + }, +} diff --git a/config/widgets/buttons/settingsWindow/button46.json b/config/widgets/buttons/settingsWindow/button46.json new file mode 100644 index 000000000..9d9b986d7 --- /dev/null +++ b/config/widgets/buttons/settingsWindow/button46.json @@ -0,0 +1,114 @@ +{ + "normal" : { + "width": 46, + "height": 32, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 46, "h": 32} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 46, "h": 32} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + ] + } + ] + }, + "pressed" : { + "width": 46, + "height": 32, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 1, "y": 1, "w": 45, "h": 31} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 46, "h": 32} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] }, + + { "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 96 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] }, + + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] }, + ] + } + ] + }, + "blocked" : { + "width": 46, + "height": 32, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 46, "h": 32} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 46, "h": 32} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + ] + } + ] + }, + "highlighted" : { + "width": 46, + "height": 32, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 46, "h": 32} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 46, "h": 32} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "rectangle", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 255, 255, 255, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 255, 255, 255, 255 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 255, 255, 255, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + ] + } + ] + }, +} diff --git a/config/widgets/buttons/settingsWindow/button80.json b/config/widgets/buttons/settingsWindow/button80.json new file mode 100644 index 000000000..c3bb58f03 --- /dev/null +++ b/config/widgets/buttons/settingsWindow/button80.json @@ -0,0 +1,114 @@ +{ + "normal" : { + "width": 80, + "height": 32, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 80, "h": 32} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 80, "h": 32} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + ] + } + ] + }, + "pressed" : { + "width": 80, + "height": 32, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 1, "y": 1, "w": 79, "h": 31} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 80, "h": 32} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] }, + + { "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 96 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] }, + + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] }, + ] + } + ] + }, + "blocked" : { + "width": 80, + "height": 32, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 80, "h": 32} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 80, "h": 32} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + ] + } + ] + }, + "highlighted" : { + "width": 80, + "height": 32, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 80, "h": 32} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 80, "h": 32} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "rectangle", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 255, 255, 255, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 255, 255, 255, 255 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 255, 255, 255, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + ] + } + ] + }, +} From 20008cd5a5a0ad851e9c0734fbb888362e4f10f9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Feb 2024 13:51:00 +0200 Subject: [PATCH 175/250] Replaced premade buttons on hero screen with generated ones --- .../heroWindow/artifactSlotEmpty.png | Bin .../heroWindow}/backpackButtonIcon.png | Bin .../Data/heroWindow/commanderButtonIcon.png | Bin 0 -> 489 bytes Mods/vcmi/Sprites/buttons/backpack.json | 8 -- Mods/vcmi/Sprites/buttons/backpackNormal.png | Bin 3308 -> 0 bytes Mods/vcmi/Sprites/buttons/backpackPressed.png | Bin 3122 -> 0 bytes Mods/vcmi/Sprites/buttons/commander.json | 8 -- Mods/vcmi/Sprites/buttons/commanderNormal.png | Bin 3232 -> 0 bytes .../vcmi/Sprites/buttons/commanderPressed.png | Bin 2858 -> 0 bytes client/windows/CHeroWindow.cpp | 7 +- client/windows/GUIClasses.cpp | 8 +- config/widgets/buttons/heroBackpack.json | 114 ++++++++++++++++++ config/widgets/buttons/heroCommander.json | 114 ++++++++++++++++++ .../buttons/settingsWindow/button190.json | 12 +- .../buttons/settingsWindow/button32.json | 12 +- .../buttons/settingsWindow/button46.json | 12 +- .../buttons/settingsWindow/button80.json | 12 +- 17 files changed, 260 insertions(+), 47 deletions(-) rename Mods/vcmi/{Sprites => Data}/heroWindow/artifactSlotEmpty.png (100%) rename Mods/vcmi/{Sprites/buttons => Data/heroWindow}/backpackButtonIcon.png (100%) create mode 100644 Mods/vcmi/Data/heroWindow/commanderButtonIcon.png delete mode 100644 Mods/vcmi/Sprites/buttons/backpack.json delete mode 100644 Mods/vcmi/Sprites/buttons/backpackNormal.png delete mode 100644 Mods/vcmi/Sprites/buttons/backpackPressed.png delete mode 100644 Mods/vcmi/Sprites/buttons/commander.json delete mode 100644 Mods/vcmi/Sprites/buttons/commanderNormal.png delete mode 100644 Mods/vcmi/Sprites/buttons/commanderPressed.png create mode 100644 config/widgets/buttons/heroBackpack.json create mode 100644 config/widgets/buttons/heroCommander.json diff --git a/Mods/vcmi/Sprites/heroWindow/artifactSlotEmpty.png b/Mods/vcmi/Data/heroWindow/artifactSlotEmpty.png similarity index 100% rename from Mods/vcmi/Sprites/heroWindow/artifactSlotEmpty.png rename to Mods/vcmi/Data/heroWindow/artifactSlotEmpty.png diff --git a/Mods/vcmi/Sprites/buttons/backpackButtonIcon.png b/Mods/vcmi/Data/heroWindow/backpackButtonIcon.png similarity index 100% rename from Mods/vcmi/Sprites/buttons/backpackButtonIcon.png rename to Mods/vcmi/Data/heroWindow/backpackButtonIcon.png diff --git a/Mods/vcmi/Data/heroWindow/commanderButtonIcon.png b/Mods/vcmi/Data/heroWindow/commanderButtonIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..aa32487fd0e7a0e21f596116886f7632fe1ec4be GIT binary patch literal 489 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 h+h%m&*v9rQr0HyayCF&J7Wm@(Point(314, 429), AnimationPath::builtin("hsbtns4.def"), CButton::tooltip(heroscrn[0]), [=](){ LOCPLINT->showQuestLog(); }, EShortcut::ADVENTURE_QUEST_LOG); - backpackButton = std::make_shared(Point(424, 429), AnimationPath::builtin("buttons/backpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), [=](){ createBackpackWindow(); }, EShortcut::HERO_BACKPACK); - backpackButton->setOverlay(std::make_shared(ImagePath::builtin("buttons/backpackButtonIcon"))); + backpackButton = std::make_shared(Point(424, 429), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), [=](){ createBackpackWindow(); }, EShortcut::HERO_BACKPACK); + backpackButton->setOverlay(std::make_shared(ImagePath::builtin("heroWindow/backpackButtonIcon"))); dismissButton = std::make_shared(Point(534, 429), AnimationPath::builtin("hsbtns2.def"), CButton::tooltip(heroscrn[28]), [=](){ dismissCurrent(); }, EShortcut::HERO_DISMISS); } else @@ -106,7 +106,8 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero) if(hero->commander) { - commanderButton = std::make_shared(Point(317, 18), AnimationPath::builtin("buttons/commander"), CButton::tooltipLocalized("vcmi.heroWindow.openCommander"), [&](){ commanderWindow(); }, EShortcut::HERO_COMMANDER); + commanderButton = std::make_shared(Point(317, 18), AnimationPath::builtin("heroCommander"), CButton::tooltipLocalized("vcmi.heroWindow.openCommander"), [&](){ commanderWindow(); }, EShortcut::HERO_COMMANDER); + commanderButton->setOverlay(std::make_shared(ImagePath::builtin("heroWindow/commanderButtonIcon"))); } //right list of heroes diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index a9de69bf9..a870f019d 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -878,12 +878,12 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, std::bind(moveArtifacts, [this](bool equipped, bool baclpack) -> void {controller.swapArtifacts(equipped, baclpack);})); moveArtifactsButtonRight = std::make_shared(Point(425, 154), AnimationPath::builtin(QUICK_EXCHANGE_MOD_PREFIX + "/artLeft.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[3]), std::bind(moveArtifacts, [this](bool equipped, bool baclpack) -> void {controller.moveArtifacts(false, equipped, baclpack);})); - backpackButtonLeft = std::make_shared(Point(325, 518), AnimationPath::builtin("buttons/backpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), + backpackButtonLeft = std::make_shared(Point(325, 518), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), std::bind(openBackpack, heroInst[0])); - backpackButtonLeft->setOverlay(std::make_shared(ImagePath::builtin("buttons/backpackButtonIcon"))); - backpackButtonRight = std::make_shared(Point(419, 518), AnimationPath::builtin("buttons/backpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), + backpackButtonLeft->setOverlay(std::make_shared(ImagePath::builtin("heroWindow/backpackButtonIcon"))); + backpackButtonRight = std::make_shared(Point(419, 518), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), std::bind(openBackpack, heroInst[1])); - backpackButtonRight->setOverlay(std::make_shared(ImagePath::builtin("buttons/backpackButtonIcon"))); + backpackButtonRight->setOverlay(std::make_shared(ImagePath::builtin("heroWindow/backpackButtonIcon"))); auto leftHeroBlock = heroInst[0]->tempOwner != LOCPLINT->cb->getPlayerID(); auto rightHeroBlock = heroInst[1]->tempOwner != LOCPLINT->cb->getPlayerID(); diff --git a/config/widgets/buttons/heroBackpack.json b/config/widgets/buttons/heroBackpack.json new file mode 100644 index 000000000..7cdbb9fa0 --- /dev/null +++ b/config/widgets/buttons/heroBackpack.json @@ -0,0 +1,114 @@ +{ + "normal" : { + "width": 52, + "height": 36, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 52, "h": 36} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 52, "h": 36} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + ] + } + ] + }, + "pressed" : { + "width": 52, + "height": 36, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 1, "y": 1, "w": 51, "h": 35} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 52, "h": 36} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] }, + + { "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] }, + + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 64 ] }, + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] }, + ] + } + ] + }, + "blocked" : { + "width": 52, + "height": 36, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 52, "h": 36} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 52, "h": 36} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + ] + } + ] + }, + "highlighted" : { + "width": 52, + "height": 36, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 52, "h": 36} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 52, "h": 36} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "rectangle", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 255, 255, 255, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + ] + } + ] + }, +} diff --git a/config/widgets/buttons/heroCommander.json b/config/widgets/buttons/heroCommander.json new file mode 100644 index 000000000..110edde34 --- /dev/null +++ b/config/widgets/buttons/heroCommander.json @@ -0,0 +1,114 @@ +{ + "normal" : { + "width": 92, + "height": 38, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 92, "h": 38} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 92, "h": 38} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + ] + } + ] + }, + "pressed" : { + "width": 92, + "height": 38, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 1, "y": 1, "w": 91, "h": 37} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 92, "h": 38} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] }, + + { "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] }, + + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 64 ] }, + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] }, + ] + } + ] + }, + "blocked" : { + "width": 92, + "height": 38, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 92, "h": 38} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 92, "h": 38} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, + + { "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, + ] + } + ] + }, + "highlighted" : { + "width": 92, + "height": 38, + "items" : [ + { + "type": "texture", + "image": "DiBoxBck", + "rect": {"x": 0, "y": 0, "w": 92, "h": 38} + }, + { + "type": "graphicalPrimitive", + "rect": {"x": 0, "y": 0, "w": 92, "h": 38} + "primitives" : [ + { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, + + { "type" : "rectangle", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 255, 255, 255, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 255 ] }, + { "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 255 ] }, + + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, + ] + } + ] + }, +} diff --git a/config/widgets/buttons/settingsWindow/button190.json b/config/widgets/buttons/settingsWindow/button190.json index de1e0f1ed..d2f88ed99 100644 --- a/config/widgets/buttons/settingsWindow/button190.json +++ b/config/widgets/buttons/settingsWindow/button190.json @@ -14,10 +14,10 @@ "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] }, { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, @@ -46,10 +46,10 @@ { "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 96 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] }, { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] }, - { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] }, ] } @@ -70,10 +70,10 @@ "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] }, { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, diff --git a/config/widgets/buttons/settingsWindow/button32.json b/config/widgets/buttons/settingsWindow/button32.json index 6d748e7d9..41b8d29b1 100644 --- a/config/widgets/buttons/settingsWindow/button32.json +++ b/config/widgets/buttons/settingsWindow/button32.json @@ -14,10 +14,10 @@ "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] }, { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, @@ -46,10 +46,10 @@ { "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 96 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] }, { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] }, - { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] }, ] } @@ -70,10 +70,10 @@ "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] }, { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, diff --git a/config/widgets/buttons/settingsWindow/button46.json b/config/widgets/buttons/settingsWindow/button46.json index 9d9b986d7..046c299ea 100644 --- a/config/widgets/buttons/settingsWindow/button46.json +++ b/config/widgets/buttons/settingsWindow/button46.json @@ -14,10 +14,10 @@ "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] }, { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, @@ -46,10 +46,10 @@ { "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 96 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] }, { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] }, - { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] }, ] } @@ -70,10 +70,10 @@ "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] }, { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, diff --git a/config/widgets/buttons/settingsWindow/button80.json b/config/widgets/buttons/settingsWindow/button80.json index c3bb58f03..1afd039a0 100644 --- a/config/widgets/buttons/settingsWindow/button80.json +++ b/config/widgets/buttons/settingsWindow/button80.json @@ -14,10 +14,10 @@ "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] }, { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, @@ -46,10 +46,10 @@ { "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 96 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] }, { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] }, - { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] }, ] } @@ -70,10 +70,10 @@ "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, - { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 128 ] }, + { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] }, { "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] }, - { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] }, + { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] }, { "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] }, { "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] }, From b7320bbc8a195ebfbeb36c540a3508873917556b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Thu, 29 Feb 2024 13:04:48 +0100 Subject: [PATCH 176/250] Cleanup --- client/lobby/RandomMapTab.cpp | 20 ++++---------------- lib/rmg/CMapGenOptions.cpp | 12 +----------- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index b6a7afb3c..a83d30757 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -590,33 +590,21 @@ void RandomMapTab::saveOptions(const CMapGenOptions & options) void RandomMapTab::loadOptions() { - // FIXME: Potential leak? - auto options = new CMapGenOptions(); - - auto rmgSettings = persistentStorage["rmg"]["rmg"]; if (!rmgSettings.Struct().empty()) { + mapGenOptions.reset(new CMapGenOptions()); JsonDeserializer handler(nullptr, rmgSettings); - handler.serializeStruct("lastSettings", *options); + handler.serializeStruct("lastSettings", *mapGenOptions); - // FIXME: Regenerate players, who are not saved - mapGenOptions.reset(options); // Will check template and set other options as well setTemplate(mapGenOptions->getMapTemplate()); if(auto w = widget("templateList")) { - // FIXME: Private function, need id w->setItem(mapGenOptions->getMapTemplate()); - logGlobal->warn("Set RMG template on drop-down list"); } - - // TODO: Else? Set default - logGlobal->warn("Loaded previous RMG settings"); - } - else - { - logGlobal->warn("Did not load previous RMG settings"); } updateMapInfoByHost(); + + // TODO: Save & load difficulty? } \ No newline at end of file diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index 26aef7842..44b7f1cef 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -837,23 +837,13 @@ void CMapGenOptions::serializeJson(JsonSerializeFormat & handler) handler.serializeString("templateName", templateName); if(!handler.saving) { - // FIXME: doesn't load correctly? Name is "Jebus Cross" setMapTemplate(templateName); - if (mapTemplate) - { - logGlobal->warn("Loaded previous RMG template"); - // FIXME: Update dropdown menu - } - else - { - logGlobal->warn("Failed to deserialize previous map template"); - } } handler.serializeIdArray("roads", enabledRoads); - //TODO: Serialize CMapGenOptions::CPlayerSettings ? This won't b saved between sessions if (!handler.saving) { + // Player settings won't be saved resetPlayersMap(); } } From a2b51323b14fd493b16a47ea9baa5e9c45e38f4d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Feb 2024 14:20:11 +0200 Subject: [PATCH 177/250] Fix json format --- config/widgets/buttons/heroBackpack.json | 8 ++++---- config/widgets/buttons/heroCommander.json | 8 ++++---- config/widgets/buttons/settingsWindow/button190.json | 8 ++++---- config/widgets/buttons/settingsWindow/button32.json | 8 ++++---- config/widgets/buttons/settingsWindow/button46.json | 8 ++++---- config/widgets/buttons/settingsWindow/button80.json | 8 ++++---- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/config/widgets/buttons/heroBackpack.json b/config/widgets/buttons/heroBackpack.json index 7cdbb9fa0..60b622da2 100644 --- a/config/widgets/buttons/heroBackpack.json +++ b/config/widgets/buttons/heroBackpack.json @@ -10,7 +10,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 52, "h": 36} + "rect": {"x": 0, "y": 0, "w": 52, "h": 36}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, @@ -40,7 +40,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 52, "h": 36} + "rect": {"x": 0, "y": 0, "w": 52, "h": 36}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] }, @@ -66,7 +66,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 52, "h": 36} + "rect": {"x": 0, "y": 0, "w": 52, "h": 36}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, @@ -96,7 +96,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 52, "h": 36} + "rect": {"x": 0, "y": 0, "w": 52, "h": 36}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, diff --git a/config/widgets/buttons/heroCommander.json b/config/widgets/buttons/heroCommander.json index 110edde34..76c2a5257 100644 --- a/config/widgets/buttons/heroCommander.json +++ b/config/widgets/buttons/heroCommander.json @@ -10,7 +10,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 92, "h": 38} + "rect": {"x": 0, "y": 0, "w": 92, "h": 38}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, @@ -40,7 +40,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 92, "h": 38} + "rect": {"x": 0, "y": 0, "w": 92, "h": 38}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] }, @@ -66,7 +66,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 92, "h": 38} + "rect": {"x": 0, "y": 0, "w": 92, "h": 38}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, @@ -96,7 +96,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 92, "h": 38} + "rect": {"x": 0, "y": 0, "w": 92, "h": 38}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, diff --git a/config/widgets/buttons/settingsWindow/button190.json b/config/widgets/buttons/settingsWindow/button190.json index d2f88ed99..341d75811 100644 --- a/config/widgets/buttons/settingsWindow/button190.json +++ b/config/widgets/buttons/settingsWindow/button190.json @@ -10,7 +10,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 190, "h": 32} + "rect": {"x": 0, "y": 0, "w": 190, "h": 32}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, @@ -40,7 +40,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 190, "h": 32} + "rect": {"x": 0, "y": 0, "w": 190, "h": 32}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] }, @@ -66,7 +66,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 190, "h": 32} + "rect": {"x": 0, "y": 0, "w": 190, "h": 32}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, @@ -96,7 +96,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 190, "h": 32} + "rect": {"x": 0, "y": 0, "w": 190, "h": 32}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, diff --git a/config/widgets/buttons/settingsWindow/button32.json b/config/widgets/buttons/settingsWindow/button32.json index 41b8d29b1..88458bd91 100644 --- a/config/widgets/buttons/settingsWindow/button32.json +++ b/config/widgets/buttons/settingsWindow/button32.json @@ -10,7 +10,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 32, "h": 24} + "rect": {"x": 0, "y": 0, "w": 32, "h": 24}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, @@ -40,7 +40,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 32, "h": 24} + "rect": {"x": 0, "y": 0, "w": 32, "h": 24}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] }, @@ -66,7 +66,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 32, "h": 24} + "rect": {"x": 0, "y": 0, "w": 32, "h": 24}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, @@ -96,7 +96,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 32, "h": 24} + "rect": {"x": 0, "y": 0, "w": 32, "h": 24}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, diff --git a/config/widgets/buttons/settingsWindow/button46.json b/config/widgets/buttons/settingsWindow/button46.json index 046c299ea..a18b6e736 100644 --- a/config/widgets/buttons/settingsWindow/button46.json +++ b/config/widgets/buttons/settingsWindow/button46.json @@ -10,7 +10,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 46, "h": 32} + "rect": {"x": 0, "y": 0, "w": 46, "h": 32}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, @@ -40,7 +40,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 46, "h": 32} + "rect": {"x": 0, "y": 0, "w": 46, "h": 32}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] }, @@ -66,7 +66,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 46, "h": 32} + "rect": {"x": 0, "y": 0, "w": 46, "h": 32}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, @@ -96,7 +96,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 46, "h": 32} + "rect": {"x": 0, "y": 0, "w": 46, "h": 32}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, diff --git a/config/widgets/buttons/settingsWindow/button80.json b/config/widgets/buttons/settingsWindow/button80.json index 1afd039a0..17c26605a 100644 --- a/config/widgets/buttons/settingsWindow/button80.json +++ b/config/widgets/buttons/settingsWindow/button80.json @@ -10,7 +10,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 80, "h": 32} + "rect": {"x": 0, "y": 0, "w": 80, "h": 32}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, @@ -40,7 +40,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 80, "h": 32} + "rect": {"x": 0, "y": 0, "w": 80, "h": 32}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 96 ] }, @@ -66,7 +66,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 80, "h": 32} + "rect": {"x": 0, "y": 0, "w": 80, "h": 32}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, @@ -96,7 +96,7 @@ }, { "type": "graphicalPrimitive", - "rect": {"x": 0, "y": 0, "w": 80, "h": 32} + "rect": {"x": 0, "y": 0, "w": 80, "h": 32}, "primitives" : [ { "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 80 ] }, From ddb2acb9c2f7027c49870fec5e21b497317d7ac5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Feb 2024 15:39:13 +0200 Subject: [PATCH 178/250] Code cleanup --- client/widgets/Buttons.cpp | 34 ++++++++++++---------------- client/widgets/Buttons.h | 12 +++++----- client/windows/CCastleInterface.cpp | 4 ++-- client/windows/CKingdomInterface.cpp | 4 ++-- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp index a3c128bb2..418a11cf1 100644 --- a/client/widgets/Buttons.cpp +++ b/client/widgets/Buttons.cpp @@ -74,7 +74,7 @@ void ButtonBase::setTextOverlay(const std::string & Text, EFonts font, ColorRGBA update(); } -void ButtonBase::setOverlay(std::shared_ptr newOverlay) +void ButtonBase::setOverlay(const std::shared_ptr& newOverlay) { overlay = newOverlay; if(overlay) @@ -133,7 +133,7 @@ void ButtonBase::setConfigurable(const JsonPath & jsonName, bool playerColoredBu image->playerColored(LOCPLINT->playerID); } -void CButton::addHoverText(EButtonState state, std::string text) +void CButton::addHoverText(EButtonState state, const std::string & text) { hoverTexts[vstd::to_underlying(state)] = text; } @@ -278,7 +278,7 @@ void CButton::clickCancel(const Point & cursorPosition) void CButton::showPopupWindow(const Point & cursorPosition) { - if(helpBox.size()) //there is no point to show window with nothing inside... + if(!helpBox.empty()) //there is no point to show window with nothing inside... CRClickPopup::createAndPush(helpBox); } @@ -333,14 +333,15 @@ ButtonBase::~ButtonBase() = default; CButton::CButton(Point position, const AnimationPath &defName, const std::pair &help, CFunctionList Callback, EShortcut key, bool playerColoredButton): ButtonBase(position, defName, key, playerColoredButton), - callback(Callback) + callback(Callback), + helpBox(help.second), + hoverable(false), + actOnDown(false), + soundDisabled(false) { defActions = 255-DISPOSE; addUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD); - - hoverable = actOnDown = soundDisabled = false; hoverTexts[0] = help.first; - helpBox=help.second; } void ButtonBase::setPlayerColor(PlayerColor player) @@ -413,14 +414,12 @@ bool CToggleBase::isSelected() const return selected; } -bool CToggleBase::canActivate() +bool CToggleBase::canActivate() const { - if (selected && !allowDeselection) - return false; - return true; + return !selected || allowDeselection; } -void CToggleBase::addCallback(std::function function) +void CToggleBase::addCallback(const std::function & function) { callback += function; } @@ -501,7 +500,7 @@ void CToggleButton::clickCancel(const Point & cursorPosition) doSelect(isSelected()); } -void CToggleGroup::addCallback(std::function callback) +void CToggleGroup::addCallback(const std::function & callback) { onChange += callback; } @@ -511,7 +510,7 @@ void CToggleGroup::resetCallback() onChange.clear(); } -void CToggleGroup::addToggle(int identifier, std::shared_ptr button) +void CToggleGroup::addToggle(int identifier, const std::shared_ptr & button) { if(auto intObj = std::dynamic_pointer_cast(button)) // hack-ish workagound to avoid diamond problem with inheritance { @@ -538,11 +537,8 @@ void CToggleGroup::setSelected(int id) void CToggleGroup::setSelectedOnly(int id) { - for(auto it = buttons.begin(); it != buttons.end(); it++) - { - int buttonId = it->first; - buttons[buttonId]->setEnabled(buttonId == id); - } + for(const auto & button : buttons) + button.second->setEnabled(button.first == id); selectionChanged(id); } diff --git a/client/widgets/Buttons.h b/client/widgets/Buttons.h index 49d5d6a87..8cef3e583 100644 --- a/client/widgets/Buttons.h +++ b/client/widgets/Buttons.h @@ -59,7 +59,7 @@ public: void setImageOrder(int state1, int state2, int state3, int state4); /// adds overlay on top of button image. Only one overlay can be active at once - void setOverlay(std::shared_ptr newOverlay); + void setOverlay(const std::shared_ptr& newOverlay); void setTextOverlay(const std::string & Text, EFonts font, ColorRGBA color); }; @@ -90,7 +90,7 @@ public: /// adds one more callback to on-click actions void addCallback(const std::function & callback); - void addHoverText(EButtonState state, std::string text); + void addHoverText(EButtonState state, const std::string & text); void block(bool on); @@ -134,7 +134,7 @@ protected: virtual void doSelect(bool on); // returns true if toggle can change its state - bool canActivate(); + bool canActivate() const; public: CToggleBase(CFunctionList callback); @@ -150,7 +150,7 @@ public: void setAllowDeselection(bool on); - void addCallback(std::function callback); + void addCallback(const std::function & callback); /// Set whether the toggle is currently enabled for user to use, this is only inplemented in ToggleButton, not for other toggles yet. virtual void setEnabled(bool enabled); @@ -186,11 +186,11 @@ public: CToggleGroup(const CFunctionList & OnChange); - void addCallback(std::function callback); + void addCallback(const std::function & callback); void resetCallback(); /// add one toggle/button into group - void addToggle(int index, std::shared_ptr button); + void addToggle(int index, const std::shared_ptr & button); /// Changes selection to specific value. Will select toggle with this ID, if present void setSelected(int id); /// in some cases, e.g. LoadGame difficulty selection, after refreshing the UI, the ToggleGroup should diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 0ae793b79..f08ff9701 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1332,11 +1332,11 @@ void CCastleInterface::recreateIcons() hall = std::make_shared(80, 413, town, true); fort = std::make_shared(122, 413, town, false); - fastTownHall = std::make_shared(Point(80, 413), AnimationPath::builtin("castleInterfaceQuickAccess"), CButton::tooltip(), [&](){ builds->enterTownHall(); }); + fastTownHall = std::make_shared(Point(80, 413), AnimationPath::builtin("castleInterfaceQuickAccess"), CButton::tooltip(), [this](){ builds->enterTownHall(); }); fastTownHall->setOverlay(std::make_shared(AnimationPath::builtin("ITMTL"), town->hallLevel())); int imageIndex = town->fortLevel() == CGTownInstance::EFortLevel::NONE ? 3 : town->fortLevel() - 1; - fastArmyPurchase = std::make_shared(Point(122, 413), AnimationPath::builtin("castleInterfaceQuickAccess"), CButton::tooltip(), [&](){ builds->enterToTheQuickRecruitmentWindow(); }); + fastArmyPurchase = std::make_shared(Point(122, 413), AnimationPath::builtin("castleInterfaceQuickAccess"), CButton::tooltip(), [this](){ builds->enterToTheQuickRecruitmentWindow(); }); fastArmyPurchase->setOverlay(std::make_shared(AnimationPath::builtin("itmcl"), imageIndex)); fastMarket = std::make_shared(Rect(163, 410, 64, 42), [&]() diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 95be9d91a..997b0bb52 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -821,11 +821,11 @@ CTownItem::CTownItem(const CGTownInstance * Town) available.push_back(std::make_shared(Point(48+37*(int)i, 78), town, (int)i, true, false)); } - fastTownHall = std::make_shared(Point(69, 31), AnimationPath::builtin("castleInterfaceQuickAccessz"), CButton::tooltip(), [&]() { std::make_shared(town)->enterTownHall(); }); + fastTownHall = std::make_shared(Point(69, 31), AnimationPath::builtin("castleInterfaceQuickAccessz"), CButton::tooltip(), [this]() { std::make_shared(town)->enterTownHall(); }); fastTownHall->setOverlay(std::make_shared(AnimationPath::builtin("ITMTL"), town->hallLevel())); int imageIndex = town->fortLevel() == CGTownInstance::EFortLevel::NONE ? 3 : town->fortLevel() - 1; - fastArmyPurchase = std::make_shared(Point(111, 31), AnimationPath::builtin("castleInterfaceQuickAccessz"), CButton::tooltip(), [&]() { std::make_shared(town)->enterToTheQuickRecruitmentWindow(); }); + fastArmyPurchase = std::make_shared(Point(111, 31), AnimationPath::builtin("castleInterfaceQuickAccessz"), CButton::tooltip(), [this]() { std::make_shared(town)->enterToTheQuickRecruitmentWindow(); }); fastArmyPurchase->setOverlay(std::make_shared(AnimationPath::builtin("itmcl"), imageIndex)); fastTavern = std::make_shared(Rect(5, 6, 58, 64), [&]() From 22f23ba6f8f3aa548ab977d34f36a088b17f5178 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Feb 2024 15:52:11 +0200 Subject: [PATCH 179/250] Restore previously disabled functionality --- client/lobby/CBonusSelection.cpp | 9 ++++---- client/widgets/Buttons.cpp | 7 +++++- client/widgets/Buttons.h | 1 + client/widgets/ComboBox.cpp | 8 +++---- client/windows/GUIClasses.cpp | 6 ++--- .../buttons/campaignBonusSelection.json | 22 +++++++++++++++++++ 6 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 config/widgets/buttons/campaignBonusSelection.json diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 7ce3b4b7b..496d2d7e6 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -299,14 +299,13 @@ void CBonusSelection::createBonusesIcons() break; } - std::shared_ptr bonusButton = std::make_shared(Point(475 + i * 68, 455), AnimationPath(), CButton::tooltip(desc.toString(), desc.toString())); + std::shared_ptr bonusButton = std::make_shared(Point(475 + i * 68, 455), AnimationPath::builtin("campaignBonusSelection"), CButton::tooltip(desc.toString(), desc.toString())); if(picNumber != -1) - picName += ":" + std::to_string(picNumber); + bonusButton->setOverlay(std::make_shared(AnimationPath::builtin(picName), picNumber)); + else + bonusButton->setOverlay(std::make_shared(ImagePath::builtin(picName))); - auto anim = GH.renderHandler().createAnimation(); - anim->setCustom(picName, 0); -//TODO: bonusButton->setImage(anim); if(CSH->campaignBonus == i) bonusButton->setBorderColor(Colors::BRIGHT_YELLOW); groupBonuses->addToggle(i, bonusButton); diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp index 418a11cf1..1666d9d72 100644 --- a/client/widgets/Buttons.cpp +++ b/client/widgets/Buttons.cpp @@ -147,6 +147,11 @@ void ButtonBase::setImageOrder(int state1, int state2, int state3, int state4) update(); } +std::shared_ptr ButtonBase::getOverlay() +{ + return overlay; +} + void ButtonBase::setStateImpl(EButtonState newState) { state = newState; @@ -335,8 +340,8 @@ CButton::CButton(Point position, const AnimationPath &defName, const std::pair getOverlay(); void setStateImpl(EButtonState state); EButtonState getState() const; diff --git a/client/widgets/ComboBox.cpp b/client/widgets/ComboBox.cpp index eed1e831f..8bf18bd69 100644 --- a/client/widgets/ComboBox.cpp +++ b/client/widgets/ComboBox.cpp @@ -168,11 +168,9 @@ ComboBox::ComboBox(Point position, const AnimationPath & defName, const std::pai void ComboBox::setItem(const void * item) { - // TODO: - //auto w = std::dynamic_pointer_cast(overlay); - - //if( w && getItemText) - // setTextOverlay(getItemText(0, item), w->font, w->color); + auto w = std::dynamic_pointer_cast(getOverlay()); + if( w && getItemText) + setTextOverlay(getItemText(0, item), w->font, w->color); if(onSetItem) onSetItem(item); diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index a870f019d..2f42729d5 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1355,7 +1355,7 @@ CHillFortWindow::CHillFortWindow(const CGHeroInstance * visitor, const CGObjectI for(int i = 0; i < slotsCount; i++) { - upgrade[i] = std::make_shared(Point(107 + i * 76, 171), AnimationPath::builtin("APHLF1R"), CButton::tooltip(getTextForSlot(SlotID(i))), [=](){ makeDeal(SlotID(i)); }, vstd::next(EShortcut::SELECT_INDEX_1, i)); + upgrade[i] = std::make_shared(Point(107 + i * 76, 171), AnimationPath::builtin("APHLF1R"), CButton::tooltip(getTextForSlot(SlotID(i))), [this, i](){ makeDeal(SlotID(i)); }, vstd::next(EShortcut::SELECT_INDEX_1, i)); for(int j : {0,1}) { @@ -1364,9 +1364,9 @@ CHillFortWindow::CHillFortWindow(const CGHeroInstance * visitor, const CGObjectI } } - upgradeAll = std::make_shared(Point(30, 231), AnimationPath::builtin("APHLF4R"), CButton::tooltip(CGI->generaltexth->allTexts[432]), [&](){ makeDeal(SlotID(slotsCount));}, EShortcut::RECRUITMENT_UPGRADE_ALL); + upgradeAll = std::make_shared(Point(30, 231), AnimationPath::builtin("APHLF4R"), CButton::tooltip(CGI->generaltexth->allTexts[432]), [this](){ makeDeal(SlotID(slotsCount));}, EShortcut::RECRUITMENT_UPGRADE_ALL); - quit = std::make_shared(Point(294, 275), AnimationPath::builtin("IOKAY.DEF"), CButton::tooltip(), std::bind(&CHillFortWindow::close, this), EShortcut::GLOBAL_ACCEPT); + quit = std::make_shared(Point(294, 275), AnimationPath::builtin("IOKAY.DEF"), CButton::tooltip(), [this](){close();}, EShortcut::GLOBAL_ACCEPT); statusbar = CGStatusBar::create(std::make_shared(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); garr = std::make_shared(Point(108, 60), 18, Point(), hero, nullptr); diff --git a/config/widgets/buttons/campaignBonusSelection.json b/config/widgets/buttons/campaignBonusSelection.json new file mode 100644 index 000000000..325609785 --- /dev/null +++ b/config/widgets/buttons/campaignBonusSelection.json @@ -0,0 +1,22 @@ +{ + "normal" : { + "width": 58, + "height": 64, + "items" : [] + }, + "pressed" : { + "width": 58, + "height": 64, + "items" : [] + }, + "blocked" : { + "width": 58, + "height": 64, + "items" : [] + }, + "highlighted" : { + "width": 58, + "height": 64, + "items" : [] + }, +} From d5a96a122a9092e7b4b2062ddda3dce1b2f36688 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Feb 2024 17:33:00 +0200 Subject: [PATCH 180/250] Add additional constructor to JsonNode from const char * Fixes an issue where due to implicit conversion JsonNode(bool) will be called instead of expected JsonNode(std::string) --- lib/json/JsonNode.cpp | 5 +++++ lib/json/JsonNode.h | 1 + 2 files changed, 6 insertions(+) diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index 443bc068a..17fbe4406 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -76,6 +76,11 @@ JsonNode::JsonNode(double number) { } +JsonNode::JsonNode(const char * string) + : data(std::string(string)) +{ +} + JsonNode::JsonNode(const std::string & string) : data(string) { diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index 83a606a3a..c4afbcae1 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -68,6 +68,7 @@ public: explicit JsonNode(uint32_t number); explicit JsonNode(int64_t number); explicit JsonNode(double number); + explicit JsonNode(const char * string); explicit JsonNode(const std::string & string); /// Create tree from Json-formatted input From 74bd6d6075841caef679f35f9c6bc3e1c795d8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Thu, 29 Feb 2024 20:05:20 +0100 Subject: [PATCH 181/250] Editor save & load template works --- mapeditor/jsonutils.cpp | 1 + mapeditor/windownewmap.cpp | 153 +++++++++++++++++++++++++++++++++---- mapeditor/windownewmap.h | 3 +- 3 files changed, 143 insertions(+), 14 deletions(-) diff --git a/mapeditor/jsonutils.cpp b/mapeditor/jsonutils.cpp index 7c8f946ac..f7ce677bb 100644 --- a/mapeditor/jsonutils.cpp +++ b/mapeditor/jsonutils.cpp @@ -69,6 +69,7 @@ QVariant toVariant(const JsonNode & node) return QVariant(node.Bool()); break; case JsonNode::JsonType::DATA_FLOAT: + case JsonNode::JsonType::DATA_INTEGER: return QVariant(node.Float()); break; case JsonNode::JsonType::DATA_STRING: diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index e3ff7d798..51d20975d 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -17,7 +17,10 @@ #include "../lib/mapping/CMapEditManager.h" #include "../lib/mapping/MapFormat.h" #include "../lib/CGeneralTextHandler.h" +#include "../lib/serializer/JsonSerializer.h" +#include "../lib/serializer/JsonDeserializer.h" +#include "jsonutils.h" #include "windownewmap.h" #include "ui_windownewmap.h" #include "mainwindow.h" @@ -52,34 +55,142 @@ WindowNewMap::WindowNewMap(QWidget *parent) : ui->cpuTeamsCombo->setItemData(i, QVariant(cpuPlayers.at(i))); } - for(auto * combo : {ui->humanCombo, ui->cpuCombo, ui->humanTeamsCombo, ui->cpuTeamsCombo}) - combo->setCurrentIndex(0); - loadUserSettings(); + bool useLoaded = loadUserSettings(); + if (!useLoaded) + { + // FIXME This will change the teams, and map sizes as well + for(auto * combo : {ui->humanCombo, ui->cpuCombo, ui->humanTeamsCombo, ui->cpuTeamsCombo}) + combo->setCurrentIndex(0); + } show(); - //setup initial parameters - int width = ui->widthTxt->text().toInt(); - int height = ui->heightTxt->text().toInt(); - mapGenOptions.setWidth(width ? width : 1); - mapGenOptions.setHeight(height ? height : 1); - bool twoLevel = ui->twoLevelCheck->isChecked(); - mapGenOptions.setHasTwoLevels(twoLevel); - updateTemplateList(); + if (!useLoaded) + { + //setup initial parameters + int width = ui->widthTxt->text().toInt(); + int height = ui->heightTxt->text().toInt(); + mapGenOptions.setWidth(width ? width : 1); + mapGenOptions.setHeight(height ? height : 1); + bool twoLevel = ui->twoLevelCheck->isChecked(); + mapGenOptions.setHasTwoLevels(twoLevel); + + // FIXME: Do not reset loaded template + updateTemplateList(); + } } WindowNewMap::~WindowNewMap() { - saveUserSettings(); delete ui; } -void WindowNewMap::loadUserSettings() +bool WindowNewMap::loadUserSettings() { + bool ret = false; + CRmgTemplate * templ = nullptr; + //load window settings QSettings s(Ui::teamName, Ui::appName); + auto generateRandom = s.value(newMapGenerateRandom); + if (generateRandom.isValid()) + { + ui->randomMapCheck->setChecked(generateRandom.toBool()); + } + + auto settings = s.value(newMapWindow); + + if (settings.isValid()) + { + mapGenOptions = CMapGenOptions(); + auto node = JsonUtils::toJson(settings); + JsonDeserializer handler(nullptr, node); + handler.serializeStruct("lastSettings", mapGenOptions); + templ = const_cast(mapGenOptions.getMapTemplate()); // Remember for later + + ui->widthTxt->setText(QString::number(mapGenOptions.getWidth())); + ui->heightTxt->setText(QString::number(mapGenOptions.getHeight())); + for(auto & sz : mapSizes) + { + if(sz.second.first == mapGenOptions.getWidth() && + sz.second.second == mapGenOptions.getHeight()) + { + ui->sizeCombo->setCurrentIndex(sz.first); + break; + } + } + + ui->twoLevelCheck->setChecked(mapGenOptions.getHasTwoLevels()); + + auto humanComboIndex = mapGenOptions.getHumanOrCpuPlayerCount(); + if (humanComboIndex == CMapGenOptions::RANDOM_SIZE) + { + humanComboIndex = 0; + } + ui->humanCombo->setCurrentIndex(humanComboIndex); + //Can't be 0 + ui->cpuCombo->setCurrentIndex(mapGenOptions.getCompOnlyPlayerCount()); + ui->humanTeamsCombo->setCurrentIndex(mapGenOptions.getTeamCount()); + ui->cpuTeamsCombo->setCurrentIndex(mapGenOptions.getCompOnlyTeamCount()); + + switch (mapGenOptions.getWaterContent()) + { + case EWaterContent::RANDOM: + ui->waterOpt1->setChecked(true); break; + case EWaterContent::NONE: + ui->waterOpt2->setChecked(true); break; + case EWaterContent::NORMAL: + ui->waterOpt3->setChecked(true); break; + case EWaterContent::ISLANDS: + ui->waterOpt4->setChecked(true); break; + } + + switch (mapGenOptions.getMonsterStrength()) + { + case EMonsterStrength::RANDOM: + ui->monsterOpt1->setChecked(true); break; + case EMonsterStrength::GLOBAL_WEAK: + ui->monsterOpt2->setChecked(true); break; + case EMonsterStrength::GLOBAL_NORMAL: + ui->monsterOpt3->setChecked(true); break; + case EMonsterStrength::GLOBAL_STRONG: + ui->monsterOpt4->setChecked(true); break; + } + + ret = true; + } + + // FIXME: This cleans the list absolutely, and removes any template set + updateTemplateList(); + mapGenOptions.setMapTemplate(templ); // Can be null + + if (templ) + { + std::string name = templ->getId(); + for (size_t i = 0; i < ui->templateCombo->count(); i++) + { + auto * t = data_cast(ui->templateCombo->itemData(i).toLongLong()); + if (t && t->getId() == name) + { + ui->templateCombo->setCurrentIndex(i); + break; + } + + /* + // FIXME: It is serialized with a scope + if (ui->templateCombo->itemText(i).toStdString() == name) + { + ui->templateCombo->setCurrentIndex(i); + break; + } + */ + } + ret = true; + } + + /* auto width = s.value(newMapWidth); if (width.isValid()) { @@ -175,11 +286,23 @@ void WindowNewMap::loadUserSettings() //Display problem on status bar } } + */ } void WindowNewMap::saveUserSettings() { QSettings s(Ui::teamName, Ui::appName); + + JsonNode data; + JsonSerializer ser(nullptr, data); + + ser.serializeStruct("lastSettings", mapGenOptions); + + auto variant = JsonUtils::toVariant(data); + s.setValue(newMapWindow, variant); + s.setValue(newMapGenerateRandom, ui->randomMapCheck->isChecked()); + + /* s.setValue(newMapWidth, ui->widthTxt->text().toInt()); s.setValue(newMapHeight, ui->heightTxt->text().toInt()); s.setValue(newMapTwoLevel, ui->twoLevelCheck->isChecked()); @@ -217,6 +340,7 @@ void WindowNewMap::saveUserSettings() { s.setValue(newMapTemplate, templateName); } + */ } void WindowNewMap::saveOptions(const CMapGenOptions & options) @@ -232,6 +356,7 @@ void WindowNewMap::loadOptions() void WindowNewMap::on_cancelButton_clicked() { + saveUserSettings(); close(); } @@ -278,6 +403,8 @@ void WindowNewMap::on_okButton_clicked() mapGenOptions.setWaterContent(water); mapGenOptions.setMonsterStrength(monster); + saveUserSettings(); + std::unique_ptr nmap; if(ui->randomMapCheck->isChecked()) { diff --git a/mapeditor/windownewmap.h b/mapeditor/windownewmap.h index 5622ef781..fd1d52db3 100644 --- a/mapeditor/windownewmap.h +++ b/mapeditor/windownewmap.h @@ -23,6 +23,7 @@ class WindowNewMap : public QDialog, public MapGenOptionsSaver { Q_OBJECT + const QString newMapWindow = "NewMapWindow/Settings"; const QString newMapWidth = "NewMapWindow/Width"; const QString newMapHeight = "NewMapWindow/Height"; const QString newMapTwoLevel = "NewMapWindow/TwoLevel"; @@ -109,7 +110,7 @@ private: void updateTemplateList(); - void loadUserSettings(); + bool loadUserSettings(); void saveUserSettings(); void saveOptions(const CMapGenOptions & options) override; From fcbe399541b2ae57e8d141ebb60de688b9039a77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Thu, 29 Feb 2024 20:12:46 +0100 Subject: [PATCH 182/250] Cleanup --- mapeditor/windownewmap.cpp | 162 +------------------------------------ 1 file changed, 2 insertions(+), 160 deletions(-) diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index 51d20975d..50fa71253 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -59,7 +59,6 @@ WindowNewMap::WindowNewMap(QWidget *parent) : bool useLoaded = loadUserSettings(); if (!useLoaded) { - // FIXME This will change the teams, and map sizes as well for(auto * combo : {ui->humanCombo, ui->cpuCombo, ui->humanTeamsCombo, ui->cpuTeamsCombo}) combo->setCurrentIndex(0); } @@ -76,7 +75,6 @@ WindowNewMap::WindowNewMap(QWidget *parent) : bool twoLevel = ui->twoLevelCheck->isChecked(); mapGenOptions.setHasTwoLevels(twoLevel); - // FIXME: Do not reset loaded template updateTemplateList(); } } @@ -91,7 +89,6 @@ bool WindowNewMap::loadUserSettings() bool ret = false; CRmgTemplate * templ = nullptr; - //load window settings QSettings s(Ui::teamName, Ui::appName); auto generateRandom = s.value(newMapGenerateRandom); @@ -124,13 +121,7 @@ bool WindowNewMap::loadUserSettings() ui->twoLevelCheck->setChecked(mapGenOptions.getHasTwoLevels()); - auto humanComboIndex = mapGenOptions.getHumanOrCpuPlayerCount(); - if (humanComboIndex == CMapGenOptions::RANDOM_SIZE) - { - humanComboIndex = 0; - } - ui->humanCombo->setCurrentIndex(humanComboIndex); - //Can't be 0 + ui->humanCombo->setCurrentIndex(mapGenOptions.getHumanOrCpuPlayerCount()); ui->cpuCombo->setCurrentIndex(mapGenOptions.getCompOnlyPlayerCount()); ui->humanTeamsCombo->setCurrentIndex(mapGenOptions.getTeamCount()); ui->cpuTeamsCombo->setCurrentIndex(mapGenOptions.getCompOnlyTeamCount()); @@ -162,131 +153,22 @@ bool WindowNewMap::loadUserSettings() ret = true; } - // FIXME: This cleans the list absolutely, and removes any template set updateTemplateList(); mapGenOptions.setMapTemplate(templ); // Can be null if (templ) { - std::string name = templ->getId(); + std::string name = templ->getName(); for (size_t i = 0; i < ui->templateCombo->count(); i++) { - auto * t = data_cast(ui->templateCombo->itemData(i).toLongLong()); - if (t && t->getId() == name) - { - ui->templateCombo->setCurrentIndex(i); - break; - } - - /* - // FIXME: It is serialized with a scope if (ui->templateCombo->itemText(i).toStdString() == name) { ui->templateCombo->setCurrentIndex(i); break; } - */ } ret = true; } - - /* - auto width = s.value(newMapWidth); - if (width.isValid()) - { - ui->widthTxt->setText(width.toString()); - } - auto height = s.value(newMapHeight); - if (height.isValid()) - { - ui->heightTxt->setText(height.toString()); - } - for(auto & sz : mapSizes) - { - if(sz.second.first == width.toInt() && sz.second.second == height.toInt()) - ui->sizeCombo->setCurrentIndex(sz.first); - } - auto twoLevel = s.value(newMapTwoLevel); - if (twoLevel.isValid()) - { - ui->twoLevelCheck->setChecked(twoLevel.toBool()); - } - auto generateRandom = s.value(newMapGenerateRandom); - if (generateRandom.isValid()) - { - ui->randomMapCheck->setChecked(generateRandom.toBool()); - } - auto players = s.value(newMapPlayers); - if (players.isValid()) - { - ui->humanCombo->setCurrentIndex(players.toInt()); - } - auto cpuPlayers = s.value(newMapCpuPlayers); - if (cpuPlayers.isValid()) - { - ui->cpuCombo->setCurrentIndex(cpuPlayers.toInt()); - } - auto teams = s.value(newMapHumanTeams); - if(teams.isValid()) - { - ui->humanTeamsCombo->setCurrentIndex(teams.toInt()); - } - auto cputeams = s.value(newMapCpuTeams); - if(cputeams.isValid()) - { - ui->cpuTeamsCombo->setCurrentIndex(cputeams.toInt()); - } - - auto waterContent = s.value(newMapWaterContent); - if (waterContent.isValid()) - { - switch (waterContent.toInt()) - { - case EWaterContent::RANDOM: - ui->waterOpt1->setChecked(true); break; - case EWaterContent::NONE: - ui->waterOpt2->setChecked(true); break; - case EWaterContent::NORMAL: - ui->waterOpt3->setChecked(true); break; - case EWaterContent::ISLANDS: - ui->waterOpt4->setChecked(true); break; - } - - } - auto monsterStrength = s.value(newMapMonsterStrength); - if (monsterStrength.isValid()) - { - switch (monsterStrength.toInt()) - { - case EMonsterStrength::RANDOM: - ui->monsterOpt1->setChecked(true); break; - case EMonsterStrength::GLOBAL_WEAK: - ui->monsterOpt2->setChecked(true); break; - case EMonsterStrength::GLOBAL_NORMAL: - ui->monsterOpt3->setChecked(true); break; - case EMonsterStrength::GLOBAL_STRONG: - ui->monsterOpt4->setChecked(true); break; - } - } - - auto templateName = s.value(newMapTemplate); - if (templateName.isValid()) - { - updateTemplateList(); - - auto* templ = VLC->tplh->getTemplate(templateName.toString().toStdString()); - if (templ) - { - ui->templateCombo->setCurrentText(templateName.toString()); - //TODO: validate inside this method - mapGenOptions.setMapTemplate(templ); - } - else - { - //Display problem on status bar - } - } - */ } void WindowNewMap::saveUserSettings() @@ -301,46 +183,6 @@ void WindowNewMap::saveUserSettings() auto variant = JsonUtils::toVariant(data); s.setValue(newMapWindow, variant); s.setValue(newMapGenerateRandom, ui->randomMapCheck->isChecked()); - - /* - s.setValue(newMapWidth, ui->widthTxt->text().toInt()); - s.setValue(newMapHeight, ui->heightTxt->text().toInt()); - s.setValue(newMapTwoLevel, ui->twoLevelCheck->isChecked()); - s.setValue(newMapGenerateRandom, ui->randomMapCheck->isChecked()); - - s.setValue(newMapPlayers,ui->humanCombo->currentIndex()); - s.setValue(newMapCpuPlayers,ui->cpuCombo->currentIndex()); - s.setValue(newMapHumanTeams, ui->humanTeamsCombo->currentIndex()); - s.setValue(newMapCpuTeams, ui->cpuTeamsCombo->currentIndex()); - - EWaterContent::EWaterContent water = EWaterContent::RANDOM; - if(ui->waterOpt1->isChecked()) - water = EWaterContent::RANDOM; - else if(ui->waterOpt2->isChecked()) - water = EWaterContent::NONE; - else if(ui->waterOpt3->isChecked()) - water = EWaterContent::NORMAL; - else if(ui->waterOpt4->isChecked()) - water = EWaterContent::ISLANDS; - s.setValue(newMapWaterContent, static_cast(water)); - - EMonsterStrength::EMonsterStrength monster = EMonsterStrength::RANDOM; - if(ui->monsterOpt1->isChecked()) - monster = EMonsterStrength::RANDOM; - else if(ui->monsterOpt2->isChecked()) - monster = EMonsterStrength::GLOBAL_WEAK; - else if(ui->monsterOpt3->isChecked()) - monster = EMonsterStrength::GLOBAL_NORMAL; - else if(ui->monsterOpt4->isChecked()) - monster = EMonsterStrength::GLOBAL_STRONG; - s.setValue(newMapMonsterStrength, static_cast(monster)); - - auto templateName = ui->templateCombo->currentText(); - if (templateName.size()) - { - s.setValue(newMapTemplate, templateName); - } - */ } void WindowNewMap::saveOptions(const CMapGenOptions & options) From 17fc1604ca7c242ad14e0c45065df781edd0fcb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Thu, 29 Feb 2024 20:20:35 +0100 Subject: [PATCH 183/250] Remove unused file --- client/lobby/RandomMapTab.h | 7 +++---- lib/CMakeLists.txt | 1 - mapeditor/windownewmap.cpp | 13 ++----------- mapeditor/windownewmap.h | 6 +----- 4 files changed, 6 insertions(+), 21 deletions(-) diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h index a18f8acfc..fa62e151d 100644 --- a/client/lobby/RandomMapTab.h +++ b/client/lobby/RandomMapTab.h @@ -15,7 +15,6 @@ #include "../../lib/GameConstants.h" #include "../../lib/rmg/CRmgTemplate.h" #include "../gui/InterfaceObjectConfigurable.h" -#include "../lib/rmg/MapGenOptionsSaver.h" VCMI_LIB_NAMESPACE_BEGIN @@ -29,7 +28,7 @@ class CLabelGroup; class CSlider; class CPicture; -class RandomMapTab : public InterfaceObjectConfigurable, public MapGenOptionsSaver +class RandomMapTab : public InterfaceObjectConfigurable { public: RandomMapTab(); @@ -38,8 +37,8 @@ public: void setMapGenOptions(std::shared_ptr opts); void setTemplate(const CRmgTemplate *); - void saveOptions(const CMapGenOptions & options) override; - void loadOptions() override; + void saveOptions(const CMapGenOptions & options); + void loadOptions(); CMapGenOptions & obtainMapGenOptions() {return *mapGenOptions;} CFunctionList, std::shared_ptr)> mapInfoChanged; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 3740de13b..566c2fdac 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -534,7 +534,6 @@ set(lib_HEADERS rmg/RmgPath.h rmg/CMapGenerator.h rmg/CMapGenOptions.h - rmg/MapGenOptionsSaver.h rmg/CRmgTemplate.h rmg/CRmgTemplateStorage.h rmg/CZonePlacer.h diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index 50fa71253..e81e4dfc7 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -169,6 +169,8 @@ bool WindowNewMap::loadUserSettings() } ret = true; } + + return ret; } void WindowNewMap::saveUserSettings() @@ -185,17 +187,6 @@ void WindowNewMap::saveUserSettings() s.setValue(newMapGenerateRandom, ui->randomMapCheck->isChecked()); } -void WindowNewMap::saveOptions(const CMapGenOptions & options) -{ - // TODO -} - -void WindowNewMap::loadOptions() -{ - mapGenOptions = CMapGenOptions(); - // TODO -} - void WindowNewMap::on_cancelButton_clicked() { saveUserSettings(); diff --git a/mapeditor/windownewmap.h b/mapeditor/windownewmap.h index fd1d52db3..1fadd28a2 100644 --- a/mapeditor/windownewmap.h +++ b/mapeditor/windownewmap.h @@ -12,14 +12,13 @@ #include #include "../lib/rmg/CMapGenOptions.h" -#include "../lib/rmg/MapGenOptionsSaver.h" namespace Ui { class WindowNewMap; } -class WindowNewMap : public QDialog, public MapGenOptionsSaver +class WindowNewMap : public QDialog { Q_OBJECT @@ -113,9 +112,6 @@ private: bool loadUserSettings(); void saveUserSettings(); - void saveOptions(const CMapGenOptions & options) override; - void loadOptions() override; - private: Ui::WindowNewMap *ui; From c87b0e6d6590739cd631a009e30b3e5d5553ecc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Thu, 29 Feb 2024 20:44:14 +0100 Subject: [PATCH 184/250] Compile fix --- mapeditor/windownewmap.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index e81e4dfc7..e31c9d57e 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -101,7 +101,6 @@ bool WindowNewMap::loadUserSettings() if (settings.isValid()) { - mapGenOptions = CMapGenOptions(); auto node = JsonUtils::toJson(settings); JsonDeserializer handler(nullptr, node); handler.serializeStruct("lastSettings", mapGenOptions); From 51386db34723f22426a374faecc67dbf3e88d3ba Mon Sep 17 00:00:00 2001 From: godric3 Date: Thu, 29 Feb 2024 22:11:28 +0100 Subject: [PATCH 185/250] map editor: fix owner selection --- mapeditor/inspector/inspector.cpp | 31 +++++++++++++++++-------------- mapeditor/inspector/inspector.h | 12 ++++++++++-- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 0b096182b..ec6e4e996 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -233,7 +233,7 @@ void Inspector::updateProperties(CGDwelling * o) { if(!o) return; - addProperty("Owner", o->tempOwner, false); + addProperty("Owner", o->tempOwner, new OwnerDelegate(controller), false); if (o->ID == Obj::RANDOM_DWELLING || o->ID == Obj::RANDOM_DWELLING_LVL) { @@ -245,15 +245,15 @@ void Inspector::updateProperties(CGDwelling * o) void Inspector::updateProperties(CGLighthouse * o) { if(!o) return; - - addProperty("Owner", o->tempOwner, false); + + addProperty("Owner", o->tempOwner, new OwnerDelegate(controller), false); } void Inspector::updateProperties(CGGarrison * o) { if(!o) return; - addProperty("Owner", o->tempOwner, false); + addProperty("Owner", o->tempOwner, new OwnerDelegate(controller), false); addProperty("Removable units", o->removableUnits, false); } @@ -261,14 +261,14 @@ void Inspector::updateProperties(CGShipyard * o) { if(!o) return; - addProperty("Owner", o->tempOwner, false); + addProperty("Owner", o->tempOwner, new OwnerDelegate(controller), false); } void Inspector::updateProperties(CGHeroPlaceholder * o) { if(!o) return; - addProperty("Owner", o->tempOwner, false); + addProperty("Owner", o->tempOwner, new OwnerDelegate(controller), false); bool type = false; if(o->heroType.has_value()) @@ -298,7 +298,7 @@ void Inspector::updateProperties(CGHeroInstance * o) { if(!o) return; - addProperty("Owner", o->tempOwner, o->ID == Obj::PRISON); //field is not editable for prison + addProperty("Owner", o->tempOwner, new OwnerDelegate(controller), o->ID == Obj::PRISON); //field is not editable for prison addProperty("Experience", o->exp, false); addProperty("Hero class", o->type ? o->type->heroClass->getNameTranslated() : "", true); @@ -368,7 +368,7 @@ void Inspector::updateProperties(CGMine * o) { if(!o) return; - addProperty("Owner", o->tempOwner, false); + addProperty("Owner", o->tempOwner, new OwnerDelegate(controller), false); addProperty("Resource", o->producedResource); addProperty("Productivity", o->producedQuantity); } @@ -474,12 +474,7 @@ void Inspector::updateProperties() addProperty("IsStatic", factory->isStaticObject()); } - auto * delegate = new InspectorDelegate(); - delegate->options.push_back({QObject::tr("neutral"), QVariant::fromValue(PlayerColor::NEUTRAL.getNum())}); - for(int p = 0; p < controller.map()->players.size(); ++p) - if(controller.map()->players[p].canAnyonePlay()) - delegate->options.push_back({QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[p]), QVariant::fromValue(PlayerColor(p).getNum())}); - addProperty("Owner", obj->tempOwner, delegate, true); + addProperty("Owner", obj->tempOwner, new OwnerDelegate(controller), true); UPDATE_OBJ_PROPERTIES(CArmedInstance); UPDATE_OBJ_PROPERTIES(CGResource); @@ -927,3 +922,11 @@ void InspectorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, QStyledItemDelegate::setModelData(editor, model, index); } } + +OwnerDelegate::OwnerDelegate(MapController & controller) +{ + options.push_back({QObject::tr("neutral"), QVariant::fromValue(PlayerColor::NEUTRAL.getNum()) }); + for(int p = 0; p < controller.map()->players.size(); ++p) + if(controller.map()->players[p].canAnyonePlay()) + options.push_back({QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[p]), QVariant::fromValue(PlayerColor(p).getNum()) }); +} diff --git a/mapeditor/inspector/inspector.h b/mapeditor/inspector/inspector.h index 94c2b80c2..97acd3e91 100644 --- a/mapeditor/inspector/inspector.h +++ b/mapeditor/inspector/inspector.h @@ -128,7 +128,7 @@ protected: { itemKey = keyItems[key]; table->setItem(table->row(itemKey), 1, itemValue); - table->setItemDelegateForRow(table->row(itemKey), delegate); + table->setItemDelegateForRow(table->row(itemKey), delegate); } else { @@ -138,7 +138,7 @@ protected: table->setRowCount(row + 1); table->setItem(row, 0, itemKey); table->setItem(row, 1, itemValue); - table->setItemDelegateForRow(row, delegate); + table->setItemDelegateForRow(row, delegate); ++row; } itemKey->setFlags(restricted ? Qt::NoItemFlags : Qt::ItemIsEnabled); @@ -171,3 +171,11 @@ public: QList> options; }; + + +class OwnerDelegate : public InspectorDelegate +{ + Q_OBJECT +public: + OwnerDelegate(MapController &); +}; \ No newline at end of file From da91e90a7b6e86023898012586557b09fe22db69 Mon Sep 17 00:00:00 2001 From: godric3 Date: Thu, 29 Feb 2024 22:38:31 +0100 Subject: [PATCH 186/250] map editor: dont suggest neutral owner for hero --- mapeditor/inspector/inspector.cpp | 8 +++++--- mapeditor/inspector/inspector.h | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index ec6e4e996..5ae80af4d 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -298,7 +298,8 @@ void Inspector::updateProperties(CGHeroInstance * o) { if(!o) return; - addProperty("Owner", o->tempOwner, new OwnerDelegate(controller), o->ID == Obj::PRISON); //field is not editable for prison + auto isPrison = o->ID == Obj::PRISON; + addProperty("Owner", o->tempOwner, new OwnerDelegate(controller, isPrison), isPrison); //field is not editable for prison addProperty("Experience", o->exp, false); addProperty("Hero class", o->type ? o->type->heroClass->getNameTranslated() : "", true); @@ -923,9 +924,10 @@ void InspectorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, } } -OwnerDelegate::OwnerDelegate(MapController & controller) +OwnerDelegate::OwnerDelegate(MapController & controller, bool addNeutral) { - options.push_back({QObject::tr("neutral"), QVariant::fromValue(PlayerColor::NEUTRAL.getNum()) }); + if(addNeutral) + options.push_back({QObject::tr("neutral"), QVariant::fromValue(PlayerColor::NEUTRAL.getNum()) }); for(int p = 0; p < controller.map()->players.size(); ++p) if(controller.map()->players[p].canAnyonePlay()) options.push_back({QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[p]), QVariant::fromValue(PlayerColor(p).getNum()) }); diff --git a/mapeditor/inspector/inspector.h b/mapeditor/inspector/inspector.h index 97acd3e91..1bb962555 100644 --- a/mapeditor/inspector/inspector.h +++ b/mapeditor/inspector/inspector.h @@ -177,5 +177,5 @@ class OwnerDelegate : public InspectorDelegate { Q_OBJECT public: - OwnerDelegate(MapController &); + OwnerDelegate(MapController & controller, bool addNeutral = true); }; \ No newline at end of file From 5f95955535505257e31bd9d96472f1fa4cc390e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 1 Mar 2024 10:57:48 +0100 Subject: [PATCH 187/250] Store and load last difficulty setting --- client/CServerHandler.cpp | 16 +++++++++++++++- client/ServerRunner.cpp | 9 +++++++-- client/ServerRunner.h | 7 ++++--- client/lobby/CLobbyScreen.cpp | 4 ++++ config/schemas/settings.json | 5 +++++ 5 files changed, 35 insertions(+), 6 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 0ac30b561..75d1051ea 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -140,6 +140,12 @@ CServerHandler::CServerHandler() { uuid = boost::uuids::to_string(boost::uuids::random_generator()()); registerTypesLobbyPacks(*applier); + + auto lastDifficulty = settings["general"]["lastDifficulty"]; + if (lastDifficulty.isNumber()) + { + si->difficulty = lastDifficulty.Integer(); + } } void CServerHandler::threadRunNetwork() @@ -193,8 +199,16 @@ void CServerHandler::startLocalServerAndConnect(bool connectToLobby) serverRunner.reset(new ServerThreadRunner()); #endif + auto si = std::make_shared(); + + auto lastDifficulty = settings["general"]["lastDifficulty"]; + if (lastDifficulty.isNumber()) + { + si->difficulty = lastDifficulty.Integer(); + } + logNetwork->trace("\tStarting local server"); - serverRunner->start(getLocalPort(), connectToLobby); + serverRunner->start(getLocalPort(), connectToLobby, si); logNetwork->trace("\tConnecting to local server"); connectToServer(getLocalHostname(), getLocalPort()); logNetwork->trace("\tWaiting for connection"); diff --git a/client/ServerRunner.cpp b/client/ServerRunner.cpp index 59cce1110..8ab4df84b 100644 --- a/client/ServerRunner.cpp +++ b/client/ServerRunner.cpp @@ -23,10 +23,15 @@ ServerThreadRunner::ServerThreadRunner() = default; ServerThreadRunner::~ServerThreadRunner() = default; -void ServerThreadRunner::start(uint16_t port, bool connectToLobby) +void ServerThreadRunner::start(uint16_t port, bool connectToLobby, std::shared_ptr startingInfo) { server = std::make_unique(port, connectToLobby, true); + if (startingInfo) + { + server->si = startingInfo; //Else use default + } + threadRunLocalServer = boost::thread([this]{ setThreadName("runServer"); server->run(); @@ -68,7 +73,7 @@ int ServerProcessRunner::exitCode() return child->exit_code(); } -void ServerProcessRunner::start(uint16_t port, bool connectToLobby) +void ServerProcessRunner::start(uint16_t port, bool connectToLobby, std::shared_ptr startingInfo) { boost::filesystem::path serverPath = VCMIDirs::get().serverPath(); boost::filesystem::path logPath = VCMIDirs::get().userLogsPath() / "server_log.txt"; diff --git a/client/ServerRunner.h b/client/ServerRunner.h index 115dcebfa..26503595e 100644 --- a/client/ServerRunner.h +++ b/client/ServerRunner.h @@ -10,11 +10,12 @@ #pragma once class CVCMIServer; +struct StartInfo; class IServerRunner { public: - virtual void start(uint16_t port, bool connectToLobby) = 0; + virtual void start(uint16_t port, bool connectToLobby, std::shared_ptr startingInfo) = 0; virtual void shutdown() = 0; virtual void wait() = 0; virtual int exitCode() = 0; @@ -28,7 +29,7 @@ class ServerThreadRunner : public IServerRunner, boost::noncopyable std::unique_ptr server; boost::thread threadRunLocalServer; public: - void start(uint16_t port, bool connectToLobby) override; + void start(uint16_t port, bool connectToLobby, std::shared_ptr startingInfo) override; void shutdown() override; void wait() override; int exitCode() override; @@ -50,7 +51,7 @@ class ServerProcessRunner : public IServerRunner, boost::noncopyable std::unique_ptr child; public: - void start(uint16_t port, bool connectToLobby) override; + void start(uint16_t port, bool connectToLobby, std::shared_ptr startingInfo) override; void shutdown() override; void wait() override; int exitCode() override; diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index f08aacdfe..262dcb7f2 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -162,6 +162,10 @@ void CLobbyScreen::startScenario(bool allowOnlyAI) tabRand->saveOptions(*CSH->si->mapGenOptions); } + // Save chosen difficulty + Settings lastDifficulty = settings.write["general"]["lastDifficulty"]; + lastDifficulty->Integer() = getCurrentDifficulty(); + if (CSH->validateGameStart(allowOnlyAI)) { CSH->sendStartGame(allowOnlyAI); diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 890b90dfa..f566c3cc1 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -28,6 +28,7 @@ "lastSave", "lastSettingsTab", "lastCampaign", + "lastDifficulty", "saveFrequency", "notifications", "extraDump", @@ -85,6 +86,10 @@ "type" : "string", "default" : "" }, + "lastDifficulty" : { + "type" : "number", + "default" : 1 + }, "saveFrequency" : { "type" : "number", "default" : 1 From 7df36279c5ef18f0254a9a6681563d6c2bb2210f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 1 Mar 2024 11:10:40 +0100 Subject: [PATCH 188/250] Compile fix? --- client/ServerRunner.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ServerRunner.h b/client/ServerRunner.h index 26503595e..0f6896824 100644 --- a/client/ServerRunner.h +++ b/client/ServerRunner.h @@ -9,6 +9,8 @@ */ #pragma once +#include "../lib/StartInfo.h" + class CVCMIServer; struct StartInfo; From 35c1ef7be284136a1c909b3860394207a5795759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 1 Mar 2024 11:18:08 +0100 Subject: [PATCH 189/250] How about now? --- client/ServerRunner.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/ServerRunner.h b/client/ServerRunner.h index 0f6896824..d045e0c75 100644 --- a/client/ServerRunner.h +++ b/client/ServerRunner.h @@ -9,10 +9,13 @@ */ #pragma once -#include "../lib/StartInfo.h" +VCMI_LIB_NAMESPACE_BEGIN + +struct StartInfo; + +VCMI_LIB_NAMESPACE_END class CVCMIServer; -struct StartInfo; class IServerRunner { From f650072da1ca81ca734c331119d05988b602eaf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 1 Mar 2024 11:46:01 +0100 Subject: [PATCH 190/250] Remove useless code --- client/CServerHandler.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 75d1051ea..4cba861a4 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -140,12 +140,6 @@ CServerHandler::CServerHandler() { uuid = boost::uuids::to_string(boost::uuids::random_generator()()); registerTypesLobbyPacks(*applier); - - auto lastDifficulty = settings["general"]["lastDifficulty"]; - if (lastDifficulty.isNumber()) - { - si->difficulty = lastDifficulty.Integer(); - } } void CServerHandler::threadRunNetwork() From 1b2d01c25faf6c4dac17c7df8e4597b74161e073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 1 Mar 2024 12:15:04 +0100 Subject: [PATCH 191/250] Add new map sizes & better layout --- mapeditor/windownewmap.ui | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/mapeditor/windownewmap.ui b/mapeditor/windownewmap.ui index 4e83d644b..3b97e1a44 100644 --- a/mapeditor/windownewmap.ui +++ b/mapeditor/windownewmap.ui @@ -51,11 +51,11 @@ 0 20 - 261 + 281 68 - + @@ -65,6 +65,12 @@ + + + 64 + 16777215 + + Qt::ImhDigitsOnly @@ -85,6 +91,12 @@ + + + 64 + 16777215 + + Qt::ImhDigitsOnly @@ -143,23 +155,23 @@ - 120 + 144 16777215 - S (36x36) + S (36x36) - M (72x72) + M (72x72) - L (108x108) + L (108x108) @@ -167,6 +179,21 @@ XL (144x144) + + + H (180x180) + + + + + XH (216x216) + + + + + G (252x252) + + From 8f1638f78a30bf4adb4d9b88fbcf96cc257210d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 1 Mar 2024 17:48:07 +0100 Subject: [PATCH 192/250] Try to not route roads through passable objects --- lib/rmg/modificators/ObjectManager.cpp | 8 +++++--- lib/rmg/modificators/RoadPlacer.cpp | 26 ++++++++++++++++++++++---- lib/rmg/modificators/RoadPlacer.h | 3 +++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/rmg/modificators/ObjectManager.cpp b/lib/rmg/modificators/ObjectManager.cpp index 8dcc24c25..b6792d497 100644 --- a/lib/rmg/modificators/ObjectManager.cpp +++ b/lib/rmg/modificators/ObjectManager.cpp @@ -597,7 +597,7 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD { objectsVisitableArea.add(instance->getVisitablePosition()); objects.push_back(&instance->object()); - if(auto * m = zone.getModificator()) + if(auto * rp = zone.getModificator()) { if (instance->object().blockVisit && !instance->object().removable) { @@ -607,7 +607,7 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD else if(instance->object().appearance->isVisitableFromTop()) { //Passable objects - m->areaForRoads().add(instance->getVisitablePosition()); + rp->areaForRoads().add(instance->getVisitablePosition()); } else if(!instance->object().appearance->isVisitableFromTop()) { @@ -621,8 +621,10 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD (!instance->object().blockingAt(tile + int3(0, 1, 0)) && instance->object().blockingAt(tile)); }); - m->areaIsolated().unite(borderAbove); + rp->areaIsolated().unite(borderAbove); } + + rp->areaVisitable().add(instance->getVisitablePosition()); } switch (instance->object().ID.toEnum()) diff --git a/lib/rmg/modificators/RoadPlacer.cpp b/lib/rmg/modificators/RoadPlacer.cpp index 4c2b2fd41..ee51342b8 100644 --- a/lib/rmg/modificators/RoadPlacer.cpp +++ b/lib/rmg/modificators/RoadPlacer.cpp @@ -52,6 +52,11 @@ rmg::Area & RoadPlacer::areaIsolated() return isolated; } +rmg::Area & RoadPlacer::areaVisitable() +{ + return visitableTiles; +} + const rmg::Area & RoadPlacer::getRoads() const { return roads; @@ -64,7 +69,9 @@ bool RoadPlacer::createRoad(const int3 & dst) rmg::Path path(searchArea); path.connect(roads); - auto simpleRoutig = [this](const int3& src, const int3& dst) + const float VISITABLE_PENALTY = 1.33f; + + auto simpleRoutig = [this, VISITABLE_PENALTY](const int3& src, const int3& dst) { if(areaIsolated().contains(dst)) { @@ -72,14 +79,19 @@ bool RoadPlacer::createRoad(const int3 & dst) } else { - return 1.0f; + auto ret = 1.0f; + if (visitableTiles.contains(src) || visitableTiles.contains(dst)) + { + ret *= VISITABLE_PENALTY; + } + return ret; } }; auto res = path.search(dst, true, simpleRoutig); if(!res.valid()) { - auto desperateRoutig = [this](const int3& src, const int3& dst) -> float + auto desperateRoutig = [this, VISITABLE_PENALTY](const int3& src, const int3& dst) -> float { //Do not allow connections straight up through object not visitable from top if(std::abs((src - dst).y) == 1) @@ -98,7 +110,13 @@ bool RoadPlacer::createRoad(const int3 & dst) } float weight = dst.dist2dSQ(src); - return weight * weight; + + auto ret = weight * weight; + if (visitableTiles.contains(src) || visitableTiles.contains(dst)) + { + ret *= VISITABLE_PENALTY; + } + return ret; }; res = path.search(dst, false, desperateRoutig); diff --git a/lib/rmg/modificators/RoadPlacer.h b/lib/rmg/modificators/RoadPlacer.h index 252973ff8..0098cf8a5 100644 --- a/lib/rmg/modificators/RoadPlacer.h +++ b/lib/rmg/modificators/RoadPlacer.h @@ -25,8 +25,10 @@ public: void addRoadNode(const int3 & node); void connectRoads(); //fills "roads" according to "roadNodes" + // TODO: Use setters? rmg::Area & areaForRoads(); rmg::Area & areaIsolated(); + rmg::Area & areaVisitable(); const rmg::Area & getRoads() const; protected: @@ -38,6 +40,7 @@ protected: rmg::Area roads; //all tiles with roads rmg::Area areaRoads; rmg::Area isolated; + rmg::Area visitableTiles; // Tiles occupied by removable or passable objects }; VCMI_LIB_NAMESPACE_END From 4e88290962289248d011b80be614580517183d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 1 Mar 2024 18:10:05 +0100 Subject: [PATCH 193/250] Actually avoid only monsters --- lib/rmg/modificators/ObjectManager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/rmg/modificators/ObjectManager.cpp b/lib/rmg/modificators/ObjectManager.cpp index b6792d497..115e9286a 100644 --- a/lib/rmg/modificators/ObjectManager.cpp +++ b/lib/rmg/modificators/ObjectManager.cpp @@ -624,7 +624,10 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD rp->areaIsolated().unite(borderAbove); } - rp->areaVisitable().add(instance->getVisitablePosition()); + if (instance->object().ID == Obj::MONSTER) + { + rp->areaVisitable().add(instance->getVisitablePosition()); + } } switch (instance->object().ID.toEnum()) From 28f76b28392f6cbe490c0ec060cadd0b4ac4d1d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Fri, 1 Mar 2024 18:17:17 +0100 Subject: [PATCH 194/250] Actually, avoid guarded object altogether --- lib/rmg/modificators/ObjectManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rmg/modificators/ObjectManager.cpp b/lib/rmg/modificators/ObjectManager.cpp index 115e9286a..a40bae77d 100644 --- a/lib/rmg/modificators/ObjectManager.cpp +++ b/lib/rmg/modificators/ObjectManager.cpp @@ -624,7 +624,7 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD rp->areaIsolated().unite(borderAbove); } - if (instance->object().ID == Obj::MONSTER) + if (object.isGuarded()) { rp->areaVisitable().add(instance->getVisitablePosition()); } From 35429eab52ce095ad9ae7238fe54e9cd84ca4564 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 25 Feb 2024 12:39:19 +0200 Subject: [PATCH 195/250] NKAI: moddable configuration --- AI/Nullkiller/AIGateway.cpp | 4 +- AI/Nullkiller/AIUtility.cpp | 2 +- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 5 ++ AI/Nullkiller/Analyzers/BuildAnalyzer.h | 1 + AI/Nullkiller/Analyzers/HeroManager.cpp | 1 + AI/Nullkiller/Behaviors/BuildingBehavior.cpp | 4 +- AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp | 3 +- .../Behaviors/GatherArmyBehavior.cpp | 6 +- .../Behaviors/RecruitHeroBehavior.cpp | 3 +- AI/Nullkiller/CMakeLists.txt | 2 + AI/Nullkiller/Engine/Nullkiller.cpp | 22 +++--- AI/Nullkiller/Engine/Nullkiller.h | 3 +- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 2 +- AI/Nullkiller/Engine/Settings.cpp | 78 +++++++++++++++++++ AI/Nullkiller/Engine/Settings.h | 42 ++++++++++ AI/Nullkiller/Pathfinding/AINodeStorage.h | 3 - config/ai/nkai/nkai-settings.json | 7 ++ config/ai/{ => nkai}/object-priorities.txt | 0 18 files changed, 158 insertions(+), 30 deletions(-) create mode 100644 AI/Nullkiller/Engine/Settings.cpp create mode 100644 AI/Nullkiller/Engine/Settings.h create mode 100644 config/ai/nkai/nkai-settings.json rename config/ai/{ => nkai}/object-priorities.txt (100%) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 0015ed995..9ad7c8421 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -677,9 +677,9 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vectorheroManager->getHeroRole(hero) != HeroRole::MAIN - || nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE)) + || nullkiller->buildAnalyzer->isGoldPreasureHigh())) { - sel = 1; // for now lets pick gold from a chest. + sel = 1; } } diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index ace41d24c..1fa07a404 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -437,7 +437,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject case Obj::MAGIC_WELL: return h->mana < h->manaLimit(); case Obj::PRISON: - return ai->cb->getHeroesInfo().size() < VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP); + return !ai->heroManager->heroCapReached(); case Obj::TAVERN: case Obj::EYE_OF_MAGI: case Obj::BOAT: diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 8a6edc8c7..74e5a3d70 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -120,6 +120,11 @@ TResources BuildAnalyzer::getTotalResourcesRequired() const return result; } +bool BuildAnalyzer::isGoldPreasureHigh() const +{ + return goldPreasure > ai->settings->getMaxGoldPreasure(); +} + void BuildAnalyzer::update() { logAi->trace("Start analysing build"); diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.h b/AI/Nullkiller/Analyzers/BuildAnalyzer.h index 43049b295..754225070 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.h +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.h @@ -96,6 +96,7 @@ public: const std::vector & getDevelopmentInfo() const { return developmentInfos; } TResources getDailyIncome() const { return dailyIncome; } float getGoldPreasure() const { return goldPreasure; } + bool isGoldPreasureHigh() const; bool hasAnyBuilding(int32_t alignment, BuildingID bid) const; private: diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 42a3ae29f..fdb0f72ae 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -187,6 +187,7 @@ bool HeroManager::heroCapReached() const int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned); return heroCount >= ALLOWED_ROAMING_HEROES + || heroCount >= ai->settings->getMaxRoamingHeroes() || heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP) || heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP); } diff --git a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp index d21b92965..8cf713954 100644 --- a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp @@ -47,13 +47,13 @@ Goals::TGoalVec BuildingBehavior::decompose() const totalDevelopmentCost.toString()); auto & developmentInfos = ai->nullkiller->buildAnalyzer->getDevelopmentInfo(); - auto goldPreasure = ai->nullkiller->buildAnalyzer->getGoldPreasure(); + auto isGoldPreasureLow = !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh(); for(auto & developmentInfo : developmentInfos) { for(auto & buildingInfo : developmentInfo.toBuild) { - if(goldPreasure < MAX_GOLD_PEASURE || buildingInfo.dailyIncome[EGameResID::GOLD] > 0) + if(isGoldPreasureLow || buildingInfo.dailyIncome[EGameResID::GOLD] > 0) { if(buildingInfo.notEnoughRes) { diff --git a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp index b5260ac3a..3e8251dfd 100644 --- a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp @@ -46,8 +46,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose() const for(const CGHeroInstance * targetHero : heroes) { - if(ai->nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE - && !town->hasBuilt(BuildingID::CITY_HALL)) + if(ai->nullkiller->buildAnalyzer->isGoldPreasureHigh() && !town->hasBuilt(BuildingID::CITY_HALL)) { continue; } diff --git a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp index 935e782f5..de1fab7f4 100644 --- a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp @@ -246,7 +246,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader) { auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero); - if(heroRole == HeroRole::MAIN && path.turn() < SCOUT_TURN_DISTANCE_LIMIT) + if(heroRole == HeroRole::MAIN && path.turn() < ai->nullkiller->settings->getScoutHeroTurnDistanceLimit()) hasMainAround = true; } @@ -335,7 +335,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader) if(!upgrade.upgradeValue && armyToGetOrBuy.upgradeValue > 20000 && ai->nullkiller->heroManager->canRecruitHero(town) - && path.turn() < SCOUT_TURN_DISTANCE_LIMIT) + && path.turn() < ai->nullkiller->settings->getScoutHeroTurnDistanceLimit()) { for(auto hero : cb->getAvailableHeroes(town)) { @@ -344,7 +344,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader) if(scoutReinforcement >= armyToGetOrBuy.upgradeValue && ai->nullkiller->getFreeGold() >20000 - && ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE) + && !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh()) { Composition recruitHero; diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 885cc7af2..91c384ff1 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -85,8 +85,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const continue; if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1 - || (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000 - && ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE)) + || (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh())) { tasks.push_back(Goals::sptr(Goals::RecruitHero(town).setpriority(3))); } diff --git a/AI/Nullkiller/CMakeLists.txt b/AI/Nullkiller/CMakeLists.txt index 8ea5f777a..c84fa0fd6 100644 --- a/AI/Nullkiller/CMakeLists.txt +++ b/AI/Nullkiller/CMakeLists.txt @@ -17,6 +17,7 @@ set(Nullkiller_SRCS AIUtility.cpp Analyzers/ArmyManager.cpp Analyzers/HeroManager.cpp + Engine/Settings.cpp Engine/FuzzyEngines.cpp Engine/FuzzyHelper.cpp Engine/AIMemory.cpp @@ -80,6 +81,7 @@ set(Nullkiller_HEADERS AIUtility.h Analyzers/ArmyManager.h Analyzers/HeroManager.h + Engine/Settings.h Engine/FuzzyEngines.h Engine/FuzzyHelper.h Engine/AIMemory.h diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index de5222570..f9620e011 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -27,15 +27,11 @@ namespace NKAI using namespace Goals; -#if NKAI_TRACE_LEVEL >= 1 -#define MAXPASS 1000000 -#else -#define MAXPASS 30 -#endif - Nullkiller::Nullkiller() + :activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true) { - memory.reset(new AIMemory()); + memory = std::make_unique(); + settings = std::make_unique(); } void Nullkiller::init(std::shared_ptr cb, PlayerColor playerID) @@ -166,12 +162,12 @@ void Nullkiller::updateAiState(int pass, bool fast) if(scanDepth == ScanDepth::SMALL) { - cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT; + cfg.mainTurnDistanceLimit = ai->nullkiller->settings->getMainHeroTurnDistanceLimit(); } if(scanDepth != ScanDepth::ALL_FULL) { - cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT; + cfg.scoutTurnDistanceLimit = ai->nullkiller->settings->getScoutHeroTurnDistanceLimit(); } boost::this_thread::interruption_point(); @@ -235,13 +231,13 @@ void Nullkiller::makeTurn() resetAiState(); - for(int i = 1; i <= MAXPASS; i++) + for(int i = 1; i <= settings->getMaxPass(); i++) { updateAiState(i); Goals::TTask bestTask = taskptr(Goals::Invalid()); - for(;i <= MAXPASS; i++) + for(;i <= settings->getMaxPass(); i++) { Goals::TTaskVec fastTasks = { choseBestTask(sptr(BuyArmyBehavior()), 1), @@ -328,9 +324,9 @@ void Nullkiller::makeTurn() executeTask(bestTask); - if(i == MAXPASS) + if(i == settings->getMaxPass()) { - logAi->error("Goal %s exceeded maxpass. Terminating AI turn.", taskDescription); + logAi->warn("Goal %s exceeded maxpass. Terminating AI turn.", taskDescription); } } } diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index b75465c73..5f1ccebb8 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -11,6 +11,7 @@ #include "PriorityEvaluator.h" #include "FuzzyHelper.h" +#include "Settings.h" #include "AIMemory.h" #include "DeepDecomposer.h" #include "../Analyzers/DangerHitMapAnalyzer.h" @@ -23,7 +24,6 @@ namespace NKAI { -const float MAX_GOLD_PEASURE = 0.3f; const float MIN_PRIORITY = 0.01f; const float SMALL_SCAN_MIN_PRIORITY = 0.4f; @@ -71,6 +71,7 @@ public: std::unique_ptr dangerEvaluator; std::unique_ptr decomposer; std::unique_ptr armyFormation; + std::unique_ptr settings; PlayerColor playerID; std::shared_ptr cb; std::mutex aiStateMutex; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 3540162c3..f9db14725 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -69,7 +69,7 @@ PriorityEvaluator::~PriorityEvaluator() void PriorityEvaluator::initVisitTile() { - auto file = CResourceHandler::get()->load(ResourcePath("config/ai/object-priorities.txt"))->readAll(); + auto file = CResourceHandler::get()->load(ResourcePath("config/ai/nkai/object-priorities.txt"))->readAll(); std::string str = std::string((char *)file.first.get(), file.second); engine = fl::FllImporter().fromString(str); armyLossPersentageVariable = engine->getInputVariable("armyLoss"); diff --git a/AI/Nullkiller/Engine/Settings.cpp b/AI/Nullkiller/Engine/Settings.cpp new file mode 100644 index 000000000..d004db24c --- /dev/null +++ b/AI/Nullkiller/Engine/Settings.cpp @@ -0,0 +1,78 @@ +/* +* Settings.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 + +#include "Settings.h" +#include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h" +#include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h" +#include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h" +#include "../../../lib/mapObjects/MapObjects.h" +#include "../../../lib/modding/CModHandler.h" +#include "../../../lib/VCMI_Lib.h" +#include "../../../lib/filesystem/Filesystem.h" +#include "../../../lib/json/JsonNode.h" + +namespace NKAI +{ + Settings::Settings() + : maxRoamingHeroes(8), + mainHeroTurnDistanceLimit(10), + scoutHeroTurnDistanceLimit(5), + maxGoldPreasure(0.3f), + maxpass(30) + { + ResourcePath resource("config/ai/nkai/nkai-settings", EResType::JSON); + + loadFromMod("core", resource); + + for(const auto & modName : VLC->modh->getActiveMods()) + { + if(CResourceHandler::get(modName)->existsResource(resource)) + loadFromMod(modName, resource); + } + } + + void Settings::loadFromMod(const std::string & modName, const ResourcePath & resource) + { + if(!CResourceHandler::get(modName)->existsResource(resource)) + { + logGlobal->error("Failed to load font %s from mod %s", resource.getName(), modName); + return; + } + + JsonNode node(JsonPath::fromResource(resource), modName); + + if(node.Struct()["maxRoamingHeroes"].isNumber()) + { + maxRoamingHeroes = node.Struct()["maxRoamingHeroes"].Integer(); + } + + if(node.Struct()["mainHeroTurnDistanceLimit"].isNumber()) + { + mainHeroTurnDistanceLimit = node.Struct()["mainHeroTurnDistanceLimit"].Integer(); + } + + if(node.Struct()["scoutHeroTurnDistanceLimit"].isNumber()) + { + scoutHeroTurnDistanceLimit = node.Struct()["scoutHeroTurnDistanceLimit"].Integer(); + } + + if(node.Struct()["maxpass"].isNumber()) + { + maxpass = node.Struct()["maxpass"].Integer(); + } + + if(node.Struct()["maxGoldPreasure"].isNumber()) + { + maxGoldPreasure = node.Struct()["maxGoldPreasure"].Float(); + } + } +} \ No newline at end of file diff --git a/AI/Nullkiller/Engine/Settings.h b/AI/Nullkiller/Engine/Settings.h new file mode 100644 index 000000000..1be6aac19 --- /dev/null +++ b/AI/Nullkiller/Engine/Settings.h @@ -0,0 +1,42 @@ +/* +* Settings.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 JsonNode; +class ResourcePath; + +VCMI_LIB_NAMESPACE_END + +namespace NKAI +{ + class Settings + { + private: + int maxRoamingHeroes; + int mainHeroTurnDistanceLimit; + int scoutHeroTurnDistanceLimit; + int maxpass; + float maxGoldPreasure; + + public: + Settings(); + + int getMaxPass() const { return maxpass; } + float getMaxGoldPreasure() const { return maxGoldPreasure; } + int getMaxRoamingHeroes() const { return maxRoamingHeroes; } + int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; } + int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; } + + private: + void loadFromMod(const std::string & modName, const ResourcePath & resource); + }; +} \ No newline at end of file diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index c986f080f..08117a5ac 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -24,9 +24,6 @@ namespace NKAI { - const int SCOUT_TURN_DISTANCE_LIMIT = 5; - const int MAIN_TURN_DISTANCE_LIMIT = 10; - namespace AIPathfinding { #ifdef ENVIRONMENT64 diff --git a/config/ai/nkai/nkai-settings.json b/config/ai/nkai/nkai-settings.json new file mode 100644 index 000000000..a77362420 --- /dev/null +++ b/config/ai/nkai/nkai-settings.json @@ -0,0 +1,7 @@ +{ + "maxRoamingHeroes" : 8, + "maxpass" : 30, + "mainHeroTurnDistanceLimit" : 10, + "scoutHeroTurnDistanceLimit" : 5, + "maxGoldPreasure" : 0.3 +} \ No newline at end of file diff --git a/config/ai/object-priorities.txt b/config/ai/nkai/object-priorities.txt similarity index 100% rename from config/ai/object-priorities.txt rename to config/ai/nkai/object-priorities.txt From ea48257100c389315064693b55635e54cbc0db19 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Sat, 2 Mar 2024 12:48:17 +0100 Subject: [PATCH 196/250] Fix SonarCloud issues Replace the use of "::value" with "std::is_abstract_v" and similar issues --- lib/int3.h | 2 +- lib/serializer/BinaryDeserializer.h | 30 ++++++++-------- lib/serializer/BinarySerializer.h | 20 +++++------ lib/serializer/CSerializer.h | 2 +- mapeditor/StdInc.h | 4 +-- scripting/lua/LuaStack.h | 34 +++++++++---------- scripting/lua/LuaWrapper.h | 6 ++-- .../api/events/SubscriptionRegistryProxy.h | 2 +- 8 files changed, 50 insertions(+), 50 deletions(-) diff --git a/lib/int3.h b/lib/int3.h index 7fe731d08..1307255d0 100644 --- a/lib/int3.h +++ b/lib/int3.h @@ -194,7 +194,7 @@ public: template int3 findClosestTile (Container & container, int3 dest) { - static_assert(std::is_same::value, + static_assert(std::is_same_v, "findClosestTile requires container."); int3 result(-1, -1, -1); diff --git a/lib/serializer/BinaryDeserializer.h b/lib/serializer/BinaryDeserializer.h index 222ed2364..5f702e190 100644 --- a/lib/serializer/BinaryDeserializer.h +++ b/lib/serializer/BinaryDeserializer.h @@ -96,7 +96,7 @@ class DLL_LINKAGE BinaryDeserializer : public CLoaderBase { static T *invoke(IGameCallback *cb) { - static_assert(!std::is_abstract::value, "Cannot call new upon abstract classes!"); + static_assert(!std::is_abstract_v, "Cannot call new upon abstract classes!"); return new T(cb); } }; @@ -171,21 +171,21 @@ public: return * this; } - template < class T, typename std::enable_if < std::is_fundamental::value && !std::is_same::value, int >::type = 0 > + template < class T, typename std::enable_if_t < std::is_fundamental_v && !std::is_same_v, int > = 0 > void load(T &data) { this->read(static_cast(&data), sizeof(data), reverseEndianess); } - template < typename T, typename std::enable_if < is_serializeable::value, int >::type = 0 > + template < typename T, typename std::enable_if_t < is_serializeable::value, int > = 0 > void load(T &data) { ////that const cast is evil because it allows to implicitly overwrite const objects when deserializing - typedef typename std::remove_const::type nonConstT; + typedef typename std::remove_const_t nonConstT; auto & hlp = const_cast(data); hlp.serialize(*this); } - template < typename T, typename std::enable_if < std::is_array::value, int >::type = 0 > + template < typename T, typename std::enable_if_t < std::is_array_v, int > = 0 > void load(T &data) { ui32 size = std::size(data); @@ -193,7 +193,7 @@ public: load(data[i]); } - template < typename T, typename std::enable_if < std::is_enum::value, int >::type = 0 > + template < typename T, typename std::enable_if_t < std::is_enum_v, int > = 0 > void load(T &data) { si32 read; @@ -201,7 +201,7 @@ public: data = static_cast(read); } - template < typename T, typename std::enable_if < std::is_same::value, int >::type = 0 > + template < typename T, typename std::enable_if_t < std::is_same_v, int > = 0 > void load(T &data) { ui8 read; @@ -209,7 +209,7 @@ public: data = static_cast(read); } - template ::value, int >::type = 0> + template , int > = 0> void load(std::vector &data) { ui32 length = readAndCheckLength(); @@ -218,7 +218,7 @@ public: load( data[i]); } - template < typename T, typename std::enable_if < std::is_pointer::value, int >::type = 0 > + template < typename T, typename std::enable_if_t < std::is_pointer_v, int > = 0 > void load(T &data) { bool isNull; @@ -232,7 +232,7 @@ public: loadPointerImpl(data); } - template < typename T, typename std::enable_if < std::is_base_of_v>, int >::type = 0 > + template < typename T, typename std::enable_if_t < std::is_base_of_v>, int > = 0 > void loadPointerImpl(T &data) { using DataType = std::remove_pointer_t; @@ -245,12 +245,12 @@ public: data = const_cast(constData); } - template < typename T, typename std::enable_if < !std::is_base_of_v>, int >::type = 0 > + template < typename T, typename std::enable_if_t < !std::is_base_of_v>, int > = 0 > void loadPointerImpl(T &data) { if(reader->smartVectorMembersSerialization) { - typedef typename std::remove_const::type>::type TObjectType; //eg: const CGHeroInstance * => CGHeroInstance + typedef typename std::remove_const_t> TObjectType; //eg: const CGHeroInstance * => CGHeroInstance typedef typename VectorizedTypeFor::type VType; //eg: CGHeroInstance -> CGobjectInstance typedef typename VectorizedIDType::type IDType; if(const auto *info = reader->getVectorizedTypeInfo()) @@ -292,8 +292,8 @@ public: if(!tid) { - typedef typename std::remove_pointer::type npT; - typedef typename std::remove_const::type ncpT; + typedef typename std::remove_pointer_t npT; + typedef typename std::remove_const_t ncpT; data = ClassObjectCreator::invoke(cb); ptrAllocated(data, pid); load(*data); @@ -326,7 +326,7 @@ public: template void load(std::shared_ptr &data) { - typedef typename std::remove_const::type NonConstT; + typedef typename std::remove_const_t NonConstT; NonConstT *internalPtr; load(internalPtr); diff --git a/lib/serializer/BinarySerializer.h b/lib/serializer/BinarySerializer.h index 07d152ad3..28da150eb 100644 --- a/lib/serializer/BinarySerializer.h +++ b/lib/serializer/BinarySerializer.h @@ -134,28 +134,28 @@ public: return * this; } - template < typename T, typename std::enable_if < std::is_same::value, int >::type = 0 > + template < typename T, typename std::enable_if_t < std::is_same_v, int > = 0 > void save(const T &data) { ui8 writ = static_cast(data); save(writ); } - template < class T, typename std::enable_if < std::is_fundamental::value && !std::is_same::value, int >::type = 0 > + template < class T, typename std::enable_if_t < std::is_fundamental_v && !std::is_same_v, int > = 0 > void save(const T &data) { // save primitive - simply dump binary data to output this->write(static_cast(&data), sizeof(data)); } - template < typename T, typename std::enable_if < std::is_enum::value, int >::type = 0 > + template < typename T, typename std::enable_if_t < std::is_enum_v, int > = 0 > void save(const T &data) { si32 writ = static_cast(data); *this & writ; } - template < typename T, typename std::enable_if < std::is_array::value, int >::type = 0 > + template < typename T, typename std::enable_if_t < std::is_array_v, int > = 0 > void save(const T &data) { ui32 size = std::size(data); @@ -163,7 +163,7 @@ public: *this & data[i]; } - template < typename T, typename std::enable_if < std::is_pointer::value, int >::type = 0 > + template < typename T, typename std::enable_if_t < std::is_pointer_v, int > = 0 > void save(const T &data) { //write if pointer is not nullptr @@ -177,17 +177,17 @@ public: savePointerImpl(data); } - template < typename T, typename std::enable_if < std::is_base_of_v>, int >::type = 0 > + template < typename T, typename std::enable_if_t < std::is_base_of_v>, int > = 0 > void savePointerImpl(const T &data) { auto index = data->getId(); save(index); } - template < typename T, typename std::enable_if < !std::is_base_of_v>, int >::type = 0 > + template < typename T, typename std::enable_if_t < !std::is_base_of_v>, int > = 0 > void savePointerImpl(const T &data) { - typedef typename std::remove_const::type>::type TObjectType; + typedef typename std::remove_const_t> TObjectType; if(writer->smartVectorMembersSerialization) { @@ -239,7 +239,7 @@ public: applier.getApplier(tid)->savePtr(*this, static_cast(data)); //call serializer specific for our real type } - template < typename T, typename std::enable_if < is_serializeable::value, int >::type = 0 > + template < typename T, typename std::enable_if_t < is_serializeable::value, int > = 0 > void save(const T &data) { const_cast(data).serialize(*this); @@ -268,7 +268,7 @@ public: T *internalPtr = data.get(); save(internalPtr); } - template ::value, int >::type = 0> + template , int > = 0> void save(const std::vector &data) { ui32 length = (ui32)data.size(); diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index d0f90c758..cc9392031 100644 --- a/lib/serializer/CSerializer.h +++ b/lib/serializer/CSerializer.h @@ -138,7 +138,7 @@ struct is_serializeable template static Yes test(U * data, S* arg1 = nullptr, typename std::enable_if_tserialize(*arg1))>> * = nullptr); static No test(...); - static const bool value = sizeof(Yes) == sizeof(is_serializeable::test((typename std::remove_reference::type>::type*)nullptr)); + static const bool value = sizeof(Yes) == sizeof(is_serializeable::test((typename std::remove_reference_t>*)nullptr)); }; template //metafunction returning CGObjectInstance if T is its derivate or T elsewise diff --git a/mapeditor/StdInc.h b/mapeditor/StdInc.h index 4dc45a2ef..f877c4191 100644 --- a/mapeditor/StdInc.h +++ b/mapeditor/StdInc.h @@ -15,8 +15,8 @@ VCMI_LIB_USING_NAMESPACE -using NumericPointer = typename std::conditional::type; +using NumericPointer = typename std::conditional_t; template NumericPointer data_cast(Type * _pointer) diff --git a/scripting/lua/LuaStack.h b/scripting/lua/LuaStack.h index 7963c1d77..3f615c0fe 100644 --- a/scripting/lua/LuaStack.h +++ b/scripting/lua/LuaStack.h @@ -26,13 +26,13 @@ namespace detail template struct IsRegularClass { - static constexpr auto value = std::is_class::value && !std::is_base_of::value; + static constexpr auto value = std::is_class_v && !std::is_base_of_v; }; template struct IsIdClass { - static constexpr auto value = std::is_class::value && std::is_base_of::value; + static constexpr auto value = std::is_class_v && std::is_base_of_v; }; } @@ -61,13 +61,13 @@ public: pushNil(); } - template::value && !std::is_same::value, int>::type = 0> + template && !std::is_same_v, int> = 0> void push(const T value) { pushInteger(static_cast(value)); } - template::value, int>::type = 0> + template, int> = 0> void push(const T value) { pushInteger(static_cast(value)); @@ -75,13 +75,13 @@ public: void push(const int3 & value); - template::value, int>::type = 0> + template::value, int> = 0> void push(const T & value) { pushInteger(static_cast(value.getNum())); } - template::value, int>::type = 0> + template::value, int> = 0> void push(T * value) { using UData = T *; @@ -107,7 +107,7 @@ public: lua_setmetatable(L, -2); } - template::value, int>::type = 0> + template::value, int> = 0> void push(std::shared_ptr value) { using UData = std::shared_ptr; @@ -133,7 +133,7 @@ public: lua_setmetatable(L, -2); } - template::value, int>::type = 0> + template::value, int> = 0> void push(std::unique_ptr && value) { using UData = std::unique_ptr; @@ -163,7 +163,7 @@ public: bool tryGet(int position, bool & value); - template::value && !std::is_same::value, int>::type = 0> + template && !std::is_same_v, int> = 0> bool tryGet(int position, T & value) { lua_Integer temp; @@ -178,7 +178,7 @@ public: } } - template::value, int>::type = 0> + template::value, int> = 0> bool tryGet(int position, T & value) { lua_Integer temp; @@ -193,7 +193,7 @@ public: } } - template::value, int>::type = 0> + template, int> = 0> bool tryGet(int position, T & value) { lua_Integer temp; @@ -213,10 +213,10 @@ public: bool tryGet(int position, double & value); bool tryGet(int position, std::string & value); - template::value && std::is_const::value, int>::type = 0> + template::value && std::is_const_v, int> = 0> STRONG_INLINE bool tryGet(int position, T * & value) { - using NCValue = typename std::remove_const::type; + using NCValue = typename std::remove_const_t; using UData = NCValue *; using CUData = T *; @@ -224,16 +224,16 @@ public: return tryGetCUData(position, value); } - template::value && !std::is_const::value, int>::type = 0> + template::value && !std::is_const_v, int> = 0> STRONG_INLINE bool tryGet(int position, T * & value) { return tryGetUData(position, value); } - template::value && std::is_const::value, int>::type = 0> + template::value && std::is_const_v, int> = 0> STRONG_INLINE bool tryGet(int position, std::shared_ptr & value) { - using NCValue = typename std::remove_const::type; + using NCValue = typename std::remove_const_t; using UData = std::shared_ptr; using CUData = std::shared_ptr; @@ -241,7 +241,7 @@ public: return tryGetCUData, UData, CUData>(position, value); } - template::value && !std::is_const::value, int>::type = 0> + template::value && !std::is_const_v, int> = 0> STRONG_INLINE bool tryGet(int position, std::shared_ptr & value) { return tryGetUData>(position, value); diff --git a/scripting/lua/LuaWrapper.h b/scripting/lua/LuaWrapper.h index 83071b3b3..a6911bb6d 100644 --- a/scripting/lua/LuaWrapper.h +++ b/scripting/lua/LuaWrapper.h @@ -124,7 +124,7 @@ template class OpaqueWrapper : public RegistarBase { public: - using ObjectType = typename std::remove_cv::type; + using ObjectType = typename std::remove_cv_t; using UDataType = ObjectType *; using CUDataType = const ObjectType *; @@ -163,7 +163,7 @@ template class SharedWrapper : public RegistarBase { public: - using ObjectType = typename std::remove_cv::type; + using ObjectType = typename std::remove_cv_t; using UDataType = std::shared_ptr; using CustomRegType = detail::CustomRegType; @@ -208,7 +208,7 @@ template class UniqueOpaqueWrapper : public api::Registar { public: - using ObjectType = typename std::remove_cv::type; + using ObjectType = typename std::remove_cv_t; using UDataType = std::unique_ptr; using CustomRegType = detail::CustomRegType; diff --git a/scripting/lua/api/events/SubscriptionRegistryProxy.h b/scripting/lua/api/events/SubscriptionRegistryProxy.h index b84002051..b4f34f93d 100644 --- a/scripting/lua/api/events/SubscriptionRegistryProxy.h +++ b/scripting/lua/api/events/SubscriptionRegistryProxy.h @@ -42,7 +42,7 @@ public: using EventType = typename EventProxy::ObjectType; using RegistryType = ::events::SubscriptionRegistry; - static_assert(std::is_base_of<::events::Event, EventType>::value, "Invalid template parameter"); + static_assert(std::is_base_of_v<::events::Event, EventType>, "Invalid template parameter"); static int subscribeBefore(lua_State * L) { From 376a17409feeef3419603a9eb7f9a7e3ab36eb79 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 20 Jan 2024 22:54:30 +0200 Subject: [PATCH 197/250] NKAI: initial implementation of object graph --- AI/Nullkiller/AIUtility.h | 4 +- .../Analyzers/DangerHitMapAnalyzer.cpp | 16 +- AI/Nullkiller/Analyzers/ObjectClusterizer.cpp | 2 +- .../Behaviors/CaptureObjectsBehavior.cpp | 4 +- AI/Nullkiller/Behaviors/ClusterBehavior.cpp | 2 +- AI/Nullkiller/CMakeLists.txt | 2 + AI/Nullkiller/Engine/Nullkiller.cpp | 10 + AI/Nullkiller/Engine/Nullkiller.h | 14 +- AI/Nullkiller/Pathfinding/AINodeStorage.h | 8 +- AI/Nullkiller/Pathfinding/AIPathfinder.cpp | 47 +++- AI/Nullkiller/Pathfinding/AIPathfinder.h | 9 +- .../Pathfinding/AIPathfinderConfig.cpp | 24 +- .../Pathfinding/AIPathfinderConfig.h | 3 +- AI/Nullkiller/Pathfinding/ObjectGraph.cpp | 237 ++++++++++++++++++ AI/Nullkiller/Pathfinding/ObjectGraph.h | 129 ++++++++++ .../Rules/AIMovementAfterDestinationRule.cpp | 9 +- .../Rules/AIMovementAfterDestinationRule.h | 6 +- 17 files changed, 476 insertions(+), 50 deletions(-) create mode 100644 AI/Nullkiller/Pathfinding/ObjectGraph.cpp create mode 100644 AI/Nullkiller/Pathfinding/ObjectGraph.h diff --git a/AI/Nullkiller/AIUtility.h b/AI/Nullkiller/AIUtility.h index b4bacdcce..3dff90b6b 100644 --- a/AI/Nullkiller/AIUtility.h +++ b/AI/Nullkiller/AIUtility.h @@ -185,8 +185,8 @@ void foreach_tile_pos(const Func & foo) } } -template -void foreach_tile_pos(CCallback * cbp, const Func & foo) // avoid costly retrieval of thread-specific pointer +template +void foreach_tile_pos(TCallback * cbp, const Func & foo) // avoid costly retrieval of thread-specific pointer { int3 mapSize = cbp->getMapSize(); for(int z = 0; z < mapSize.z; z++) diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index 76ac640f7..74daae3a1 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -75,8 +75,7 @@ void DangerHitMapAnalyzer::updateHitMap() PathfinderSettings ps; - ps.mainTurnDistanceLimit = 10; - ps.scoutTurnDistanceLimit = 10; + ps.scoutTurnDistanceLimit = ps.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT; ps.useHeroChain = false; ai->pathfinder->updatePaths(pair.second, ps); @@ -160,9 +159,6 @@ void DangerHitMapAnalyzer::calculateTileOwners() std::map townHeroes; std::map heroTownMap; - PathfinderSettings pathfinderSettings; - - pathfinderSettings.mainTurnDistanceLimit = 5; auto addTownHero = [&](const CGTownInstance * town) { @@ -192,7 +188,10 @@ void DangerHitMapAnalyzer::calculateTileOwners() addTownHero(town); } - ai->pathfinder->updatePaths(townHeroes, PathfinderSettings()); + PathfinderSettings ps; + ps.mainTurnDistanceLimit = ps.scoutTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT; + + ai->pathfinder->updatePaths(townHeroes, ps); pforeachTilePos(mapSize, [&](const int3 & pos) { @@ -239,6 +238,11 @@ void DangerHitMapAnalyzer::calculateTileOwners() hitMap[pos.x][pos.y][pos.z].closestTown = enemyTown; } }); + + for(auto h : townHeroes) + { + delete h.first; + } } const std::vector & DangerHitMapAnalyzer::getTownThreats(const CGTownInstance * town) const diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index a228f1b4d..6d204e3a4 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -244,7 +244,7 @@ void ObjectClusterizer::clusterize() logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); #endif - auto paths = ai->pathfinder->getPathInfo(obj->visitablePos()); + auto paths = ai->pathfinder->getPathInfo(obj->visitablePos(), true); if(paths.empty()) { diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index 9ce74a72f..951df981c 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -179,7 +179,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const const int3 pos = objToVisit->visitablePos(); - auto paths = ai->nullkiller->pathfinder->getPathInfo(pos); + auto paths = ai->nullkiller->pathfinder->getPathInfo(pos, true); std::vector> waysToVisitObj; std::shared_ptr closestWay; @@ -210,7 +210,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const { captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects()); - if(tasks.empty() || ai->nullkiller->getScanDepth() != ScanDepth::SMALL) + if(tasks.empty()) captureObjects(ai->nullkiller->objectClusterizer->getFarObjects()); } diff --git a/AI/Nullkiller/Behaviors/ClusterBehavior.cpp b/AI/Nullkiller/Behaviors/ClusterBehavior.cpp index cc376acb8..4b185ed90 100644 --- a/AI/Nullkiller/Behaviors/ClusterBehavior.cpp +++ b/AI/Nullkiller/Behaviors/ClusterBehavior.cpp @@ -42,7 +42,7 @@ Goals::TGoalVec ClusterBehavior::decompose() const Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr cluster) const { auto center = cluster->calculateCenter(); - auto paths = ai->nullkiller->pathfinder->getPathInfo(center->visitablePos()); + auto paths = ai->nullkiller->pathfinder->getPathInfo(center->visitablePos(), true); auto blockerPos = cluster->blocker->visitablePos(); std::vector blockerPaths; diff --git a/AI/Nullkiller/CMakeLists.txt b/AI/Nullkiller/CMakeLists.txt index c84fa0fd6..15e54c395 100644 --- a/AI/Nullkiller/CMakeLists.txt +++ b/AI/Nullkiller/CMakeLists.txt @@ -14,6 +14,7 @@ set(Nullkiller_SRCS Pathfinding/Rules/AIMovementAfterDestinationRule.cpp Pathfinding/Rules/AIMovementToDestinationRule.cpp Pathfinding/Rules/AIPreviousNodeRule.cpp + Pathfinding/ObjectGraph.cpp AIUtility.cpp Analyzers/ArmyManager.cpp Analyzers/HeroManager.cpp @@ -78,6 +79,7 @@ set(Nullkiller_HEADERS Pathfinding/Rules/AIMovementAfterDestinationRule.h Pathfinding/Rules/AIMovementToDestinationRule.h Pathfinding/Rules/AIPreviousNodeRule.h + Pathfinding/ObjectGraph.h AIUtility.h Analyzers/ArmyManager.h Analyzers/HeroManager.h diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index f9620e011..520ae182b 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -27,6 +27,9 @@ namespace NKAI using namespace Goals; +// while we play vcmieagles graph can be shared +std::unique_ptr Nullkiller::baseGraph; + Nullkiller::Nullkiller() :activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true) { @@ -119,6 +122,12 @@ void Nullkiller::resetAiState() lockedHeroes.clear(); dangerHitMap->reset(); useHeroChain = true; + + if(!baseGraph) + { + baseGraph = std::make_unique(); + baseGraph->updateGraph(this); + } } void Nullkiller::updateAiState(int pass, bool fast) @@ -173,6 +182,7 @@ void Nullkiller::updateAiState(int pass, bool fast) boost::this_thread::interruption_point(); pathfinder->updatePaths(activeHeroes, cfg); + pathfinder->updateGraphs(activeHeroes); boost::this_thread::interruption_point(); diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 5f1ccebb8..2f5588967 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -25,7 +25,6 @@ namespace NKAI { const float MIN_PRIORITY = 0.01f; -const float SMALL_SCAN_MIN_PRIORITY = 0.4f; enum class HeroLockedReason { @@ -38,15 +37,6 @@ enum class HeroLockedReason HERO_CHAIN = 3 }; -enum class ScanDepth -{ - MAIN_FULL = 0, - - SMALL = 1, - - ALL_FULL = 2 -}; - class Nullkiller { private: @@ -54,11 +44,12 @@ private: int3 targetTile; ObjectInstanceID targetObject; std::map lockedHeroes; - ScanDepth scanDepth; TResources lockedResources; bool useHeroChain; public: + static std::unique_ptr baseGraph; + std::unique_ptr dangerHitMap; std::unique_ptr buildAnalyzer; std::unique_ptr objectClusterizer; @@ -94,7 +85,6 @@ public: int32_t getFreeGold() const { return getFreeResources()[EGameResID::GOLD]; } void lockResources(const TResources & res); const TResources & getLockedResources() const { return lockedResources; } - ScanDepth getScanDepth() const { return scanDepth; } private: void resetAiState(); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 08117a5ac..297ac0a57 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -26,13 +26,9 @@ namespace NKAI { namespace AIPathfinding { -#ifdef ENVIRONMENT64 - const int BUCKET_COUNT = 7; -#else - const int BUCKET_COUNT = 5; -#endif // ENVIRONMENT64 - const int BUCKET_SIZE = 5; + const int BUCKET_COUNT = 5; + const int BUCKET_SIZE = 3; const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE; const int THREAD_COUNT = 8; const int CHAIN_MAX_DEPTH = 4; diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp index b51d19bbe..bc208bcdb 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp @@ -33,7 +33,7 @@ bool AIPathfinder::isTileAccessible(const HeroPtr & hero, const int3 & tile) con || storage->isTileAccessible(hero, tile, EPathfindingLayer::SAIL); } -std::vector AIPathfinder::getPathInfo(const int3 & tile) const +std::vector AIPathfinder::getPathInfo(const int3 & tile, bool includeGraph) const { const TerrainTile * tileInfo = cb->getTile(tile, false); @@ -42,7 +42,19 @@ std::vector AIPathfinder::getPathInfo(const int3 & tile) const return std::vector(); } - return storage->getChainInfo(tile, !tileInfo->isWater()); + auto info = storage->getChainInfo(tile, !tileInfo->isWater()); + + if(includeGraph) + { + for(auto hero : cb->getHeroesInfo()) + { + auto & graph = heroGraphs.at(hero->id); + + graph.addChainInfo(info, tile, hero, ai); + } + } + + return info; } void AIPathfinder::updatePaths(std::map heroes, PathfinderSettings pathfinderSettings) @@ -71,7 +83,7 @@ void AIPathfinder::updatePaths(std::map heroes storage->setTownsAndDwellings(cb->getTownsInfo(), ai->memory->visitableObjs); } - auto config = std::make_shared(cb, ai, storage); + auto config = std::make_shared(cb, ai, storage, pathfinderSettings.allowBypassObjects); logAi->trace("Recalculate paths pass %d", pass++); cb->calculatePaths(config); @@ -112,4 +124,33 @@ void AIPathfinder::updatePaths(std::map heroes logAi->trace("Recalculated paths in %ld", timeElapsed(start)); } +void AIPathfinder::updateGraphs(std::map heroes) +{ + auto start = std::chrono::high_resolution_clock::now(); + std::vector heroesVector; + + heroGraphs.clear(); + + for(auto hero : heroes) + { + heroGraphs.emplace(hero.first->id, GraphPaths()); + heroesVector.push_back(hero.first); + } + + parallel_for(blocked_range(0, heroesVector.size()), [&](const blocked_range & r) + { + for(auto i = r.begin(); i != r.end(); i++) + heroGraphs[heroesVector[i]->id].calculatePaths(heroesVector[i], ai); + }); + +#if NKAI_TRACE_LEVEL >= 2 + for(auto hero : heroes) + { + heroGraphs[hero.first->id].dumpToLog(); + } +#endif + + logAi->trace("Graph paths updated in %lld", timeElapsed(start)); +} + } diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.h b/AI/Nullkiller/Pathfinding/AIPathfinder.h index eb7b92f44..7f98b8edc 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.h +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.h @@ -11,6 +11,7 @@ #pragma once #include "AINodeStorage.h" +#include "ObjectGraph.h" #include "../AIUtility.h" namespace NKAI @@ -23,11 +24,13 @@ struct PathfinderSettings bool useHeroChain; uint8_t scoutTurnDistanceLimit; uint8_t mainTurnDistanceLimit; + bool allowBypassObjects; PathfinderSettings() :useHeroChain(false), scoutTurnDistanceLimit(255), - mainTurnDistanceLimit(255) + mainTurnDistanceLimit(255), + allowBypassObjects(true) { } }; @@ -37,12 +40,14 @@ private: std::shared_ptr storage; CPlayerSpecificInfoCallback * cb; Nullkiller * ai; + std::map heroGraphs; public: AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai); - std::vector getPathInfo(const int3 & tile) const; + std::vector getPathInfo(const int3 & tile, bool includeGraph = false) const; bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const; void updatePaths(std::map heroes, PathfinderSettings pathfinderSettings); + void updateGraphs(std::map heroes); void init(); }; diff --git a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp index 2259ef029..ed1d4c698 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp @@ -24,16 +24,17 @@ namespace AIPathfinding std::vector> makeRuleset( CPlayerSpecificInfoCallback * cb, Nullkiller * ai, - std::shared_ptr nodeStorage) + std::shared_ptr nodeStorage, + bool allowBypassObjects) { - std::vector> rules = { - std::make_shared(cb, ai, nodeStorage), - std::make_shared(), - std::make_shared(nodeStorage), - std::make_shared(), - std::make_shared(nodeStorage), - std::make_shared(cb, nodeStorage) - }; + std::vector> rules = { + std::make_shared(cb, ai, nodeStorage), + std::make_shared(), + std::make_shared(nodeStorage), + std::make_shared(), + std::make_shared(nodeStorage), + std::make_shared(cb, nodeStorage, allowBypassObjects) + }; return rules; } @@ -41,8 +42,9 @@ namespace AIPathfinding AIPathfinderConfig::AIPathfinderConfig( CPlayerSpecificInfoCallback * cb, Nullkiller * ai, - std::shared_ptr nodeStorage) - :PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), aiNodeStorage(nodeStorage) + std::shared_ptr nodeStorage, + bool allowBypassObjects) + :PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage) { options.canUseCast = true; } diff --git a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h index 2f242e767..1619b6e93 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h +++ b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h @@ -30,7 +30,8 @@ namespace AIPathfinding AIPathfinderConfig( CPlayerSpecificInfoCallback * cb, Nullkiller * ai, - std::shared_ptr nodeStorage); + std::shared_ptr nodeStorage, + bool allowBypassObjects); ~AIPathfinderConfig(); diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp new file mode 100644 index 000000000..f1696a736 --- /dev/null +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp @@ -0,0 +1,237 @@ +/* +* ObjectGraph.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 "ObjectGraph.h" +#include "AIPathfinderConfig.h" +#include "../../../CCallback.h" +#include "../../../lib/mapping/CMap.h" +#include "../Engine/Nullkiller.h" + +namespace NKAI +{ + +void ObjectGraph::updateGraph(const Nullkiller * ai) +{ + auto cb = ai->cb; + auto mapSize = cb->getMapSize(); + + std::map actors; + std::map actorObjectMap; + + auto addObjectActor = [&](const CGObjectInstance * obj) + { + auto objectActor = new CGHeroInstance(obj->cb); + CRandomGenerator rng; + auto visitablePos = obj->visitablePos(); + + objectActor->setOwner(ai->playerID); // lets avoid having multiple colors + objectActor->initHero(rng, static_cast(0)); + objectActor->pos = objectActor->convertFromVisitablePos(visitablePos); + objectActor->initObj(rng); + + actorObjectMap[objectActor] = obj; + actors[objectActor] = obj->ID == Obj::TOWN ? HeroRole::MAIN : HeroRole::SCOUT; + addObject(obj); + }; + + for(auto obj : ai->memory->visitableObjs) + { + if(obj && obj->isVisitable() && obj->ID != Obj::HERO) + { + addObjectActor(obj); + } + } + + for(auto town : cb->getTownsInfo()) + { + addObjectActor(town); + } + + PathfinderSettings ps; + + ps.mainTurnDistanceLimit = 5; + ps.scoutTurnDistanceLimit = 1; + ps.allowBypassObjects = false; + + ai->pathfinder->updatePaths(actors, ps); + + foreach_tile_pos(cb.get(), [&](const CPlayerSpecificInfoCallback * cb, const int3 & pos) + { + auto paths = ai->pathfinder->getPathInfo(pos); + + for(AIPath & path1 : paths) + { + for(AIPath & path2 : paths) + { + if(path1.targetHero == path2.targetHero) + continue; + + auto obj1 = actorObjectMap[path1.targetHero]; + auto obj2 = actorObjectMap[path2.targetHero]; + + nodes[obj1->visitablePos()].connections[obj2->visitablePos()].update( + path1.movementCost() + path2.movementCost(), + path1.getPathDanger() + path2.getPathDanger()); + } + } + }); + + for(auto h : actors) + { + delete h.first; + } +} + +void ObjectGraph::addObject(const CGObjectInstance * obj) +{ + nodes[obj->visitablePos()].init(obj); +} + +void ObjectGraph::connectHeroes(const Nullkiller * ai) +{ + for(auto obj : ai->memory->visitableObjs) + { + if(obj && obj->ID == Obj::HERO) + { + addObject(obj); + } + } + + for(auto node : nodes) + { + auto pos = node.first; + auto paths = ai->pathfinder->getPathInfo(pos); + + for(AIPath & path : paths) + { + if(path.turn() == 0) + continue; + + auto heroPos = path.targetHero->visitablePos(); + + nodes[pos].connections[heroPos].update( + path.movementCost(), + path.getPathDanger()); + + nodes[heroPos].connections[pos].update( + path.movementCost(), + path.getPathDanger()); + } + } +} + +bool GraphNodeComparer::operator()(int3 lhs, int3 rhs) const +{ + return pathNodes.at(lhs).cost > pathNodes.at(rhs).cost; +} + +void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai) +{ + graph = *ai->baseGraph; + graph.connectHeroes(ai); + + pathNodes.clear(); + + GraphNodeComparer cmp(pathNodes); + GraphPathNode::TFibHeap pq(cmp); + + pathNodes[targetHero->visitablePos()].cost = 0; + pq.emplace(targetHero->visitablePos()); + + while(!pq.empty()) + { + int3 pos = pq.top(); + pq.pop(); + + auto node = pathNodes[pos]; + + node.isInQueue = false; + + graph.iterateConnections(pos, [&](int3 target, ObjectLink o) + { + auto & targetNode = pathNodes[target]; + + if(targetNode.tryUpdate(pos, node, o)) + { + if(targetNode.isInQueue) + { + pq.increase(targetNode.handle); + } + else + { + targetNode.handle = pq.emplace(target); + targetNode.isInQueue = true; + } + } + }); + } +} + +void GraphPaths::dumpToLog() const +{ + for(auto & node : pathNodes) + { + logAi->trace( + "%s -> %s: %f !%d", + node.second.previous.toString(), + node.first.toString(), + node.second.cost, + node.second.danger); + } +} + +void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const +{ + auto node = pathNodes.find(tile); + + if(node == pathNodes.end() || !node->second.reachable()) + return; + + std::vector tilesToPass; + + uint64_t danger = node->second.danger; + float cost = node->second.cost;; + + while(node != pathNodes.end() && node->second.cost > 1) + { + vstd::amax(danger, node->second.danger); + + tilesToPass.push_back(node->first); + node = pathNodes.find(node->second.previous); + } + + if(tilesToPass.empty()) + return; + + auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back()); + + for(auto & path : entryPaths) + { + if(path.targetHero != hero) + continue; + + for(auto graphTile : tilesToPass) + { + AIPathNodeInfo n; + + n.coord = graphTile; + n.cost = cost; + n.turns = static_cast(cost) + 1; // just in case lets select worst scenario + n.danger = danger; + n.targetHero = hero; + + path.nodes.insert(path.nodes.begin(), n); + } + + paths.push_back(path); + } +} + +} diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.h b/AI/Nullkiller/Pathfinding/ObjectGraph.h new file mode 100644 index 000000000..fade57659 --- /dev/null +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.h @@ -0,0 +1,129 @@ +/* +* ObjectGraph.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 "AINodeStorage.h" +#include "../AIUtility.h" + +namespace NKAI +{ + +class Nullkiller; + +struct ObjectLink +{ + float cost = 100000; // some big number + uint64_t danger = 0; + + void update(float newCost, uint64_t newDanger) + { + if(cost > newCost) + { + cost = newCost; + danger = newDanger; + } + } +}; + +struct ObjectNode +{ + ObjectInstanceID objID; + bool objectExists; + std::unordered_map connections; + + void init(const CGObjectInstance * obj) + { + objectExists = true; + objID = obj->id; + } +}; + +class ObjectGraph +{ + std::unordered_map nodes; + +public: + void updateGraph(const Nullkiller * ai); + void addObject(const CGObjectInstance * obj); + void connectHeroes(const Nullkiller * ai); + + template + void iterateConnections(const int3 & pos, Func fn) + { + for(auto connection : nodes.at(pos).connections) + { + fn(connection.first, connection.second); + } + } +}; + +struct GraphPathNode; + +class GraphNodeComparer +{ + const std::unordered_map & pathNodes; + +public: + GraphNodeComparer(const std::unordered_map & pathNodes) + :pathNodes(pathNodes) + { + } + + bool operator()(int3 lhs, int3 rhs) const; +}; + +struct GraphPathNode +{ + const float BAD_COST = 100000; + + int3 previous = int3(-1); + float cost = BAD_COST; + uint64_t danger = 0; + + using TFibHeap = boost::heap::fibonacci_heap>; + + TFibHeap::handle_type handle; + bool isInQueue = false; + + bool reachable() const + { + return cost < BAD_COST; + } + + bool tryUpdate(const int3 & pos, const GraphPathNode & prev, const ObjectLink & link) + { + auto newCost = prev.cost + link.cost; + + if(newCost < cost) + { + previous = pos; + danger = prev.danger + link.danger; + cost = newCost; + + return true; + } + + return false; + } +}; + +class GraphPaths +{ + ObjectGraph graph; + std::unordered_map pathNodes; + +public: + void calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai); + void addChainInfo(std::vector & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const; + void dumpToLog() const; +}; + +} diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp index 7f6664a22..9119a6759 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -13,6 +13,7 @@ #include "../Actions/QuestAction.h" #include "../../Goals/Invalid.h" #include "AIPreviousNodeRule.h" +#include "../../../../lib/pathfinder/PathfinderOptions.h" namespace NKAI { @@ -20,8 +21,9 @@ namespace AIPathfinding { AIMovementAfterDestinationRule::AIMovementAfterDestinationRule( CPlayerSpecificInfoCallback * cb, - std::shared_ptr nodeStorage) - :cb(cb), nodeStorage(nodeStorage) + std::shared_ptr nodeStorage, + bool allowBypassObjects) + :cb(cb), nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects) { } @@ -47,6 +49,9 @@ namespace AIPathfinding return; } + if(!allowBypassObjects) + return; + #if NKAI_PATHFINDER_TRACE_LEVEL >= 2 logAi->trace( "Movement from tile %s is blocked. Try to bypass. Action: %d, blocker: %d", diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h index cbaf3909a..35b91a45d 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h @@ -25,9 +25,13 @@ namespace AIPathfinding private: CPlayerSpecificInfoCallback * cb; std::shared_ptr nodeStorage; + bool allowBypassObjects; public: - AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr nodeStorage); + AIMovementAfterDestinationRule( + CPlayerSpecificInfoCallback * cb, + std::shared_ptr nodeStorage, + bool allowBypassObjects); virtual void process( const PathNodeInfo & source, From a0904416723453a62109fac9124ef240aaaf33bf Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 27 Jan 2024 22:19:27 +0200 Subject: [PATCH 198/250] NKAI: object graph second layer not operational yet --- AI/Nullkiller/Engine/Nullkiller.cpp | 1 + AI/Nullkiller/Pathfinding/AINodeStorage.h | 2 +- AI/Nullkiller/Pathfinding/AIPathfinder.h | 5 + AI/Nullkiller/Pathfinding/ObjectGraph.cpp | 151 +++++++++++++++------- AI/Nullkiller/Pathfinding/ObjectGraph.h | 63 ++++++--- 5 files changed, 150 insertions(+), 72 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 520ae182b..97998d9ae 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -168,6 +168,7 @@ void Nullkiller::updateAiState(int pass, bool fast) PathfinderSettings cfg; cfg.useHeroChain = useHeroChain; + cfg.allowBypassObjects = true; if(scanDepth == ScanDepth::SMALL) { diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 297ac0a57..f449b146d 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -11,7 +11,7 @@ #pragma once #define NKAI_PATHFINDER_TRACE_LEVEL 0 -#define NKAI_TRACE_LEVEL 0 +#define NKAI_TRACE_LEVEL 2 #include "../../../lib/pathfinder/CGPathNode.h" #include "../../../lib/pathfinder/INodeStorage.h" diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.h b/AI/Nullkiller/Pathfinding/AIPathfinder.h index 7f98b8edc..07e3c5e6a 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.h +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.h @@ -49,6 +49,11 @@ public: void updatePaths(std::map heroes, PathfinderSettings pathfinderSettings); void updateGraphs(std::map heroes); void init(); + + std::shared_ptrgetStorage() + { + return storage; + } }; } diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp index f1696a736..f15b69ee4 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp @@ -111,9 +111,6 @@ void ObjectGraph::connectHeroes(const Nullkiller * ai) for(AIPath & path : paths) { - if(path.turn() == 0) - continue; - auto heroPos = path.targetHero->visitablePos(); nodes[pos].connections[heroPos].update( @@ -127,9 +124,9 @@ void ObjectGraph::connectHeroes(const Nullkiller * ai) } } -bool GraphNodeComparer::operator()(int3 lhs, int3 rhs) const +bool GraphNodeComparer::operator()(const GraphPathNodePointer & lhs, const GraphPathNodePointer & rhs) const { - return pathNodes.at(lhs).cost > pathNodes.at(rhs).cost; + return pathNodes.at(lhs.coord)[lhs.nodeType].cost > pathNodes.at(rhs.coord)[rhs.nodeType].cost; } void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai) @@ -142,21 +139,22 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil GraphNodeComparer cmp(pathNodes); GraphPathNode::TFibHeap pq(cmp); - pathNodes[targetHero->visitablePos()].cost = 0; - pq.emplace(targetHero->visitablePos()); + pathNodes[targetHero->visitablePos()][GrapthPathNodeType::NORMAL].cost = 0; + pq.emplace(GraphPathNodePointer(targetHero->visitablePos(), GrapthPathNodeType::NORMAL)); while(!pq.empty()) { - int3 pos = pq.top(); + GraphPathNodePointer pos = pq.top(); pq.pop(); - auto node = pathNodes[pos]; + auto & node = getNode(pos); node.isInQueue = false; - graph.iterateConnections(pos, [&](int3 target, ObjectLink o) + graph.iterateConnections(pos.coord, [&](int3 target, ObjectLink o) { - auto & targetNode = pathNodes[target]; + auto targetPointer = GraphPathNodePointer(target, pos.nodeType); + auto & targetNode = getNode(targetPointer); if(targetNode.tryUpdate(pos, node, o)) { @@ -166,7 +164,7 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil } else { - targetNode.handle = pq.emplace(target); + targetNode.handle = pq.emplace(targetPointer); targetNode.isInQueue = true; } } @@ -176,61 +174,114 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil void GraphPaths::dumpToLog() const { - for(auto & node : pathNodes) + for(auto & tile : pathNodes) { - logAi->trace( - "%s -> %s: %f !%d", - node.second.previous.toString(), - node.first.toString(), - node.second.cost, - node.second.danger); + for(auto & node : tile.second) + { + if(!node.previous.valid()) + continue; + + logAi->trace( + "%s -> %s: %f !%d", + node.previous.coord.toString(), + tile.first.toString(), + node.cost, + node.danger); + } } } +bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link) +{ + auto newCost = prev.cost + link.cost; + + if(newCost < cost) + { + previous = pos; + danger = prev.danger + link.danger; + cost = newCost; + + return true; + } + + return false; +} + void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const { - auto node = pathNodes.find(tile); + auto nodes = pathNodes.find(tile); - if(node == pathNodes.end() || !node->second.reachable()) + if(nodes == pathNodes.end()) return; - std::vector tilesToPass; - - uint64_t danger = node->second.danger; - float cost = node->second.cost;; - - while(node != pathNodes.end() && node->second.cost > 1) + for(auto & node : (*nodes).second) { - vstd::amax(danger, node->second.danger); - - tilesToPass.push_back(node->first); - node = pathNodes.find(node->second.previous); - } - - if(tilesToPass.empty()) - return; - - auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back()); - - for(auto & path : entryPaths) - { - if(path.targetHero != hero) + if(!node.reachable()) continue; - for(auto graphTile : tilesToPass) + std::vector tilesToPass; + + uint64_t danger = node.danger; + float cost = node.cost; + bool allowBattle = false; + + auto current = GraphPathNodePointer(nodes->first, node.nodeType); + + while(true) { - AIPathNodeInfo n; + auto currentTile = pathNodes.find(current.coord); - n.coord = graphTile; - n.cost = cost; - n.turns = static_cast(cost) + 1; // just in case lets select worst scenario - n.danger = danger; - n.targetHero = hero; + if(currentTile == pathNodes.end()) + break; - path.nodes.insert(path.nodes.begin(), n); + auto currentNode = currentTile->second[current.nodeType]; + + allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE; + vstd::amax(danger, currentNode.danger); + vstd::amax(cost, currentNode.cost); + + tilesToPass.push_back(current.coord); + + if(currentNode.cost < 2) + break; + + current = currentNode.previous; } - paths.push_back(path); + if(tilesToPass.empty()) + continue; + + auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back()); + + for(auto & path : entryPaths) + { + if(path.targetHero != hero) + continue; + + for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++) + { + AIPathNodeInfo n; + + n.coord = *graphTile; + n.cost = cost; + n.turns = static_cast(cost) + 1; // just in case lets select worst scenario + n.danger = danger; + n.targetHero = hero; + + for(auto & node : path.nodes) + { + node.parentIndex++; + } + + path.nodes.insert(path.nodes.begin(), n); + } + + path.armyLoss += ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), danger); + path.targetObjectDanger = ai->pathfinder->getStorage()->evaluateDanger(tile, path.targetHero, !allowBattle); + path.targetObjectArmyLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger); + + paths.push_back(path); + } } } diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.h b/AI/Nullkiller/Pathfinding/ObjectGraph.h index fade57659..1dc135a68 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraph.h +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.h @@ -67,28 +67,57 @@ public: struct GraphPathNode; +enum GrapthPathNodeType +{ + NORMAL, + + BATTLE, + + LAST +}; + +struct GraphPathNodePointer +{ + int3 coord = int3(-1); + GrapthPathNodeType nodeType = GrapthPathNodeType::NORMAL; + + GraphPathNodePointer() = default; + + GraphPathNodePointer(int3 coord, GrapthPathNodeType type) + :coord(coord), nodeType(type) + { } + + bool valid() const + { + return coord.valid(); + } +}; + +typedef std::unordered_map GraphNodeStorage; + class GraphNodeComparer { - const std::unordered_map & pathNodes; + const GraphNodeStorage & pathNodes; public: - GraphNodeComparer(const std::unordered_map & pathNodes) + GraphNodeComparer(const GraphNodeStorage & pathNodes) :pathNodes(pathNodes) { } - bool operator()(int3 lhs, int3 rhs) const; + bool operator()(const GraphPathNodePointer & lhs, const GraphPathNodePointer & rhs) const; }; struct GraphPathNode { const float BAD_COST = 100000; - int3 previous = int3(-1); + GrapthPathNodeType nodeType = GrapthPathNodeType::NORMAL; + GraphPathNodePointer previous; float cost = BAD_COST; uint64_t danger = 0; - using TFibHeap = boost::heap::fibonacci_heap>; + using TFibHeap = boost::heap::fibonacci_heap>; TFibHeap::handle_type handle; bool isInQueue = false; @@ -98,32 +127,24 @@ struct GraphPathNode return cost < BAD_COST; } - bool tryUpdate(const int3 & pos, const GraphPathNode & prev, const ObjectLink & link) - { - auto newCost = prev.cost + link.cost; - - if(newCost < cost) - { - previous = pos; - danger = prev.danger + link.danger; - cost = newCost; - - return true; - } - - return false; - } + bool tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link); }; class GraphPaths { ObjectGraph graph; - std::unordered_map pathNodes; + GraphNodeStorage pathNodes; public: void calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai); void addChainInfo(std::vector & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const; void dumpToLog() const; + +private: + GraphPathNode & getNode(const GraphPathNodePointer & pos) + { + return pathNodes[pos.coord][pos.nodeType]; + } }; } From 047e076d0566980e8ec64ae71b6c48b7cce78332 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 3 Feb 2024 12:19:48 +0200 Subject: [PATCH 199/250] NKAI: visual logger --- client/ClientCommandManager.cpp | 12 ++++++ client/ClientCommandManager.h | 3 ++ client/mapView/MapView.cpp | 33 +++++++++++++++ lib/CMakeLists.txt | 2 + lib/logging/VisualLogger.cpp | 42 ++++++++++++++++++ lib/logging/VisualLogger.h | 75 +++++++++++++++++++++++++++++++++ 6 files changed, 167 insertions(+) create mode 100644 lib/logging/VisualLogger.cpp create mode 100644 lib/logging/VisualLogger.h diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index cf02eead0..8a2ea754c 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -37,6 +37,7 @@ #include "../lib/modding/ModUtility.h" #include "../lib/CHeroHandler.h" #include "../lib/VCMIDirs.h" +#include "../lib/logging/VisualLogger.h" #include "CMT.h" #ifdef SCRIPTING_ENABLED @@ -427,6 +428,14 @@ void ClientCommandManager::handleCrashCommand() //disaster! } +void ClientCommandManager::handleVsLog(std::istringstream & singleWordBuffer) +{ + std::string key; + singleWordBuffer >> key; + + logVisual->setKey(key); +} + void ClientCommandManager::printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType) { switch(messageType) @@ -546,6 +555,9 @@ void ClientCommandManager::processCommand(const std::string & message, bool call else if(commandName == "crash") handleCrashCommand(); + else if(commandName == "vslog") + handleVsLog(singleWordBuffer); + else { if (!commandName.empty() && !vstd::iswithin(commandName[0], 0, ' ')) // filter-out debugger/IDE noise diff --git a/client/ClientCommandManager.h b/client/ClientCommandManager.h index ecb555212..8f4100197 100644 --- a/client/ClientCommandManager.h +++ b/client/ClientCommandManager.h @@ -81,6 +81,9 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a // Crashes the game forcing an exception void handleCrashCommand(); + // shows object graph + void handleVsLog(std::istringstream & singleWordBuffer); + // Prints in Chat the given message void printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType = ELogLevel::NOT_SET); void giveTurn(const PlayerColor &color); diff --git a/client/mapView/MapView.cpp b/client/mapView/MapView.cpp index d1375f1df..be6124b6b 100644 --- a/client/mapView/MapView.cpp +++ b/client/mapView/MapView.cpp @@ -31,6 +31,7 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/logging/VisualLogger.h" BasicMapView::~BasicMapView() = default; @@ -57,11 +58,43 @@ BasicMapView::BasicMapView(const Point & offset, const Point & dimensions) pos.h = dimensions.y; } +class VisualLoggerRenderer : public ILogVisualizer +{ +private: + Canvas & target; + std::shared_ptr model; + +public: + VisualLoggerRenderer(Canvas & target, std::shared_ptr model) : target(target), model(model) + { + } + + virtual void drawLine(int3 start, int3 end) override + { + auto level = model->getLevel(); + + if(start.z != level || end.z != level) + return; + + auto pStart = model->getTargetTileArea(start).topLeft(); + auto pEnd = model->getTargetTileArea(end).topLeft(); + auto viewPort = target.getRenderArea(); + + if(viewPort.isInside(pStart) && viewPort.isInside(pEnd)) + { + target.drawLine(pStart, pEnd, ColorRGBA(255, 255, 0), ColorRGBA(255, 255, 0)); + } + } +}; + void BasicMapView::render(Canvas & target, bool fullUpdate) { Canvas targetClipped(target, pos); tilesCache->update(controller->getContext()); tilesCache->render(controller->getContext(), targetClipped, fullUpdate); + + VisualLoggerRenderer r(target, model); + logVisual->visualize(r); } void BasicMapView::tick(uint32_t msPassed) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 566c2fdac..bf580fc5c 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -76,6 +76,7 @@ set(lib_SRCS logging/CBasicLogConfigurator.cpp logging/CLogger.cpp + logging/VisualLogger.cpp mapObjectConstructors/AObjectTypeHandler.cpp mapObjectConstructors/CBankInstanceConstructor.cpp @@ -424,6 +425,7 @@ set(lib_HEADERS logging/CBasicLogConfigurator.h logging/CLogger.h + logging/VisualLogger.h mapObjectConstructors/AObjectTypeHandler.h mapObjectConstructors/CBankInstanceConstructor.h diff --git a/lib/logging/VisualLogger.cpp b/lib/logging/VisualLogger.cpp new file mode 100644 index 000000000..a29fa40d4 --- /dev/null +++ b/lib/logging/VisualLogger.cpp @@ -0,0 +1,42 @@ +/* + * CLogger.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 "VisualLogger.h" + +VCMI_LIB_NAMESPACE_BEGIN + +DLL_LINKAGE VisualLogger * logVisual = new VisualLogger(); + + +void VisualLogger::updateWithLock(std::string channel, std::function func) +{ + std::lock_guard lock(mutex); + + mapLines[channel].clear(); + + func(VisualLogBuilder(mapLines[channel])); +} + +void VisualLogger::visualize(ILogVisualizer & visulizer) +{ + std::lock_guard lock(mutex); + + for(auto line : mapLines[keyToShow]) + { + visulizer.drawLine(line.start, line.end); + } +} + +void VisualLogger::setKey(std::string key) +{ + keyToShow = key; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/logging/VisualLogger.h b/lib/logging/VisualLogger.h new file mode 100644 index 000000000..8e8e6de25 --- /dev/null +++ b/lib/logging/VisualLogger.h @@ -0,0 +1,75 @@ +/* + * VisualLogger.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 "../int3.h" +#include "../constants/EntityIdentifiers.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class ILogVisualizer +{ +public: + virtual void drawLine(int3 start, int3 end) = 0; +}; + +class IVisualLogBuilder +{ +public: + virtual void addLine(int3 start, int3 end) = 0; +}; + +/// The logger is used to show screen overlay +class DLL_LINKAGE VisualLogger +{ +private: + struct MapLine + { + int3 start; + int3 end; + + MapLine(int3 start, int3 end) + :start(start), end(end) + { + } + }; + + class VisualLogBuilder : public IVisualLogBuilder + { + private: + std::vector & mapLines; + + public: + VisualLogBuilder(std::vector & mapLines) + :mapLines(mapLines) + { + } + + virtual void addLine(int3 start, int3 end) override + { + mapLines.push_back(MapLine(start, end)); + } + }; + +private: + std::map> mapLines; + std::mutex mutex; + std::string keyToShow; + +public: + + void updateWithLock(std::string channel, std::function func); + void visualize(ILogVisualizer & visulizer); + void setKey(std::string key); +}; + +extern DLL_LINKAGE VisualLogger * logVisual; + +VCMI_LIB_NAMESPACE_END From b236384356c9671799e318041760b02f7fc637da Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 3 Feb 2024 12:20:59 +0200 Subject: [PATCH 200/250] NKAI: graph add battle layer --- AI/Nullkiller/Analyzers/ObjectClusterizer.cpp | 14 ++- AI/Nullkiller/Engine/Nullkiller.cpp | 2 + AI/Nullkiller/Goals/AdventureSpellCast.cpp | 10 +- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 8 +- AI/Nullkiller/Pathfinding/AINodeStorage.h | 3 +- AI/Nullkiller/Pathfinding/AIPathfinder.cpp | 9 +- .../Pathfinding/AIPathfinderConfig.cpp | 2 +- AI/Nullkiller/Pathfinding/ObjectGraph.cpp | 110 +++++++++++++++--- AI/Nullkiller/Pathfinding/ObjectGraph.h | 23 +++- .../Rules/AIMovementAfterDestinationRule.cpp | 2 +- .../Rules/AIMovementToDestinationRule.cpp | 33 ++++-- .../Rules/AIMovementToDestinationRule.h | 3 +- lib/logging/VisualLogger.cpp | 4 +- 13 files changed, 176 insertions(+), 47 deletions(-) diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 6d204e3a4..908f6c6cd 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -224,7 +224,11 @@ void ObjectClusterizer::clusterize() ai->memory->visitableObjs.begin(), ai->memory->visitableObjs.end()); +#if NKAI_TRACE_LEVEL == 0 parallel_for(blocked_range(0, objs.size()), [&](const blocked_range & r) +#else + blocked_range r(0, objs.size()); +#endif { auto priorityEvaluator = ai->priorityEvaluators->acquire(); @@ -255,9 +259,9 @@ void ObjectClusterizer::clusterize() } std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool - { - return p1.movementCost() < p2.movementCost(); - }); + { + return p1.movementCost() < p2.movementCost(); + }); if(vstd::contains(ignoreObjects, obj->ID)) { @@ -348,7 +352,11 @@ void ObjectClusterizer::clusterize() #endif } } +#if NKAI_TRACE_LEVEL == 0 }); +#else + } +#endif logAi->trace("Near objects count: %i", nearObjects.objects.size()); logAi->trace("Far objects count: %i", farObjects.objects.size()); diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 97998d9ae..e71ed1abc 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -42,6 +42,8 @@ void Nullkiller::init(std::shared_ptr cb, PlayerColor playerID) this->cb = cb; this->playerID = playerID; + baseGraph.reset(); + priorityEvaluator.reset(new PriorityEvaluator(this)); priorityEvaluators.reset( new SharedPool( diff --git a/AI/Nullkiller/Goals/AdventureSpellCast.cpp b/AI/Nullkiller/Goals/AdventureSpellCast.cpp index 7e62e7d42..17b24c0c5 100644 --- a/AI/Nullkiller/Goals/AdventureSpellCast.cpp +++ b/AI/Nullkiller/Goals/AdventureSpellCast.cpp @@ -39,12 +39,18 @@ void AdventureSpellCast::accept(AIGateway * ai) if(hero->mana < hero->getSpellCost(spell)) throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated()); - if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero) - throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated()); if(town && spellID == SpellID::TOWN_PORTAL) { ai->selectedObject = town->id; + + if(town->visitingHero && town->tempOwner == ai->playerID && !town->getUpperArmy()->stacksCount()) + { + ai->myCb->swapGarrisonHero(town); + } + + if(town->visitingHero) + throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated()); } auto wait = cb->waitTillRealize; diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 310f9d867..6c6f59d87 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1123,14 +1123,14 @@ void AINodeStorage::calculateTownPortal( { for(const CGTownInstance * targetTown : towns) { - // TODO: allow to hide visiting hero in garrison - if(targetTown->visitingHero && maskMap.find(targetTown->visitingHero.get()) != maskMap.end()) + if(targetTown->visitingHero + && targetTown->getUpperArmy()->stacksCount() + && maskMap.find(targetTown->visitingHero.get()) != maskMap.end()) { auto basicMask = maskMap.at(targetTown->visitingHero.get()); - bool heroIsInChain = (actor->chainMask & basicMask) != 0; bool sameActorInTown = actor->chainMask == basicMask; - if(sameActorInTown || !heroIsInChain) + if(!sameActorInTown) continue; } diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index f449b146d..556402d76 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -11,7 +11,8 @@ #pragma once #define NKAI_PATHFINDER_TRACE_LEVEL 0 -#define NKAI_TRACE_LEVEL 2 +#define NKAI_GRAPH_TRACE_LEVEL 0 +#define NKAI_TRACE_LEVEL 0 #include "../../../lib/pathfinder/CGPathNode.h" #include "../../../lib/pathfinder/INodeStorage.h" diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp index bc208bcdb..10d7f9644 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp @@ -48,9 +48,10 @@ std::vector AIPathfinder::getPathInfo(const int3 & tile, bool includeGra { for(auto hero : cb->getHeroesInfo()) { - auto & graph = heroGraphs.at(hero->id); + auto graph = heroGraphs.find(hero->id); - graph.addChainInfo(info, tile, hero, ai); + if(graph != heroGraphs.end()) + graph->second.addChainInfo(info, tile, hero, ai); } } @@ -140,10 +141,10 @@ void AIPathfinder::updateGraphs(std::map heroe parallel_for(blocked_range(0, heroesVector.size()), [&](const blocked_range & r) { for(auto i = r.begin(); i != r.end(); i++) - heroGraphs[heroesVector[i]->id].calculatePaths(heroesVector[i], ai); + heroGraphs.at(heroesVector[i]->id).calculatePaths(heroesVector[i], ai); }); -#if NKAI_TRACE_LEVEL >= 2 +#if NKAI_GRAPH_TRACE_LEVEL >= 1 for(auto hero : heroes) { heroGraphs[hero.first->id].dumpToLog(); diff --git a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp index ed1d4c698..afd046bc8 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp @@ -30,7 +30,7 @@ namespace AIPathfinding std::vector> rules = { std::make_shared(cb, ai, nodeStorage), std::make_shared(), - std::make_shared(nodeStorage), + std::make_shared(nodeStorage, allowBypassObjects), std::make_shared(), std::make_shared(nodeStorage), std::make_shared(cb, nodeStorage, allowBypassObjects) diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp index f15b69ee4..00d52f661 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp @@ -13,6 +13,7 @@ #include "../../../CCallback.h" #include "../../../lib/mapping/CMap.h" #include "../Engine/Nullkiller.h" +#include "../../../lib/logging/VisualLogger.h" namespace NKAI { @@ -64,6 +65,14 @@ void ObjectGraph::updateGraph(const Nullkiller * ai) foreach_tile_pos(cb.get(), [&](const CPlayerSpecificInfoCallback * cb, const int3 & pos) { + if(nodes.find(pos) != nodes.end()) + return; + + auto guarded = ai->cb->getGuardingCreaturePosition(pos).valid(); + + if(guarded) + return; + auto paths = ai->pathfinder->getPathInfo(pos); for(AIPath & path1 : paths) @@ -76,9 +85,25 @@ void ObjectGraph::updateGraph(const Nullkiller * ai) auto obj1 = actorObjectMap[path1.targetHero]; auto obj2 = actorObjectMap[path2.targetHero]; - nodes[obj1->visitablePos()].connections[obj2->visitablePos()].update( + auto danger = ai->pathfinder->getStorage()->evaluateDanger(obj2->visitablePos(), path1.targetHero, true); + + auto updated = nodes[obj1->visitablePos()].connections[obj2->visitablePos()].update( path1.movementCost() + path2.movementCost(), - path1.getPathDanger() + path2.getPathDanger()); + danger); + +#if NKAI_GRAPH_TRACE_LEVEL >= 2 + if(updated) + { + logAi->trace( + "Connected %s[%s] -> %s[%s] through [%s], cost %2f", + obj1->getObjectName(), obj1->visitablePos().toString(), + obj2->getObjectName(), obj2->visitablePos().toString(), + pos.toString(), + path1.movementCost() + path2.movementCost()); + } +#else + (void)updated; +#endif } } }); @@ -87,6 +112,10 @@ void ObjectGraph::updateGraph(const Nullkiller * ai) { delete h.first; } + +#if NKAI_GRAPH_TRACE_LEVEL >= 1 + dumpToLog("graph"); +#endif } void ObjectGraph::addObject(const CGObjectInstance * obj) @@ -104,7 +133,7 @@ void ObjectGraph::connectHeroes(const Nullkiller * ai) } } - for(auto node : nodes) + for(auto & node : nodes) { auto pos = node.first; auto paths = ai->pathfinder->getPathInfo(pos); @@ -124,6 +153,29 @@ void ObjectGraph::connectHeroes(const Nullkiller * ai) } } +void ObjectGraph::dumpToLog(std::string visualKey) const +{ + logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder) + { + for(auto & tile : nodes) + { + for(auto & node : tile.second.connections) + { +#if NKAI_GRAPH_TRACE_LEVEL >= 2 + logAi->trace( + "%s -> %s: %f !%d", + node.first.toString(), + tile.first.toString(), + node.second.cost, + node.second.danger); +#endif + + logBuilder.addLine(node.first, tile.first); + } + } + }); +} + bool GraphNodeComparer::operator()(const GraphPathNodePointer & lhs, const GraphPathNodePointer & rhs) const { return pathNodes.at(lhs.coord)[lhs.nodeType].cost > pathNodes.at(rhs.coord)[rhs.nodeType].cost; @@ -134,6 +186,7 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil graph = *ai->baseGraph; graph.connectHeroes(ai); + visualKey = std::to_string(ai->playerID) + ":" + targetHero->getNameTranslated(); pathNodes.clear(); GraphNodeComparer cmp(pathNodes); @@ -153,11 +206,17 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil graph.iterateConnections(pos.coord, [&](int3 target, ObjectLink o) { - auto targetPointer = GraphPathNodePointer(target, pos.nodeType); + auto graphNode = graph.getNode(target); + auto targetNodeType = o.danger ? GrapthPathNodeType::BATTLE : pos.nodeType; + + auto targetPointer = GraphPathNodePointer(target, targetNodeType); auto & targetNode = getNode(targetPointer); if(targetNode.tryUpdate(pos, node, o)) { + if(graph.getNode(target).objTypeID == Obj::HERO) + return; + if(targetNode.isInQueue) { pq.increase(targetNode.handle); @@ -174,21 +233,28 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil void GraphPaths::dumpToLog() const { - for(auto & tile : pathNodes) - { - for(auto & node : tile.second) + logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder) { - if(!node.previous.valid()) - continue; + for(auto & tile : pathNodes) + { + for(auto & node : tile.second) + { + if(!node.previous.valid()) + continue; - logAi->trace( - "%s -> %s: %f !%d", - node.previous.coord.toString(), - tile.first.toString(), - node.cost, - node.danger); - } - } +#if NKAI_GRAPH_TRACE_LEVEL >= 2 + logAi->trace( + "%s -> %s: %f !%d", + node.previous.coord.toString(), + tile.first.toString(), + node.cost, + node.danger); +#endif + + logBuilder.addLine(node.previous.coord, tile.first); + } + } + }); } bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link) @@ -197,6 +263,11 @@ bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathN if(newCost < cost) { + if(nodeType < pos.nodeType) + { + logAi->error("Linking error"); + } + previous = pos; danger = prev.danger + link.danger; cost = newCost; @@ -214,7 +285,7 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe if(nodes == pathNodes.end()) return; - for(auto & node : (*nodes).second) + for(auto & node : nodes->second) { if(!node.reachable()) continue; @@ -236,6 +307,9 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe auto currentNode = currentTile->second[current.nodeType]; + if(currentNode.cost == 0) + break; + allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE; vstd::amax(danger, currentNode.danger); vstd::amax(cost, currentNode.cost); diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.h b/AI/Nullkiller/Pathfinding/ObjectGraph.h index 1dc135a68..11b9e84e3 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraph.h +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.h @@ -23,19 +23,24 @@ struct ObjectLink float cost = 100000; // some big number uint64_t danger = 0; - void update(float newCost, uint64_t newDanger) + bool update(float newCost, uint64_t newDanger) { if(cost > newCost) { cost = newCost; danger = newDanger; + + return true; } + + return false; } }; struct ObjectNode { ObjectInstanceID objID; + MapObjectID objTypeID; bool objectExists; std::unordered_map connections; @@ -43,6 +48,7 @@ struct ObjectNode { objectExists = true; objID = obj->id; + objTypeID = obj->ID; } }; @@ -54,15 +60,21 @@ public: void updateGraph(const Nullkiller * ai); void addObject(const CGObjectInstance * obj); void connectHeroes(const Nullkiller * ai); + void dumpToLog(std::string visualKey) const; template void iterateConnections(const int3 & pos, Func fn) { - for(auto connection : nodes.at(pos).connections) + for(auto & connection : nodes.at(pos).connections) { fn(connection.first, connection.second); } } + + const ObjectNode & getNode(int3 tile) const + { + return nodes.at(tile); + } }; struct GraphPathNode; @@ -134,6 +146,7 @@ class GraphPaths { ObjectGraph graph; GraphNodeStorage pathNodes; + std::string visualKey; public: void calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai); @@ -143,7 +156,11 @@ public: private: GraphPathNode & getNode(const GraphPathNodePointer & pos) { - return pathNodes[pos.coord][pos.nodeType]; + auto & node = pathNodes[pos.coord][pos.nodeType]; + + node.nodeType = pos.nodeType; + + return node; } }; diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp index 9119a6759..9aa5719ae 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -48,7 +48,7 @@ namespace AIPathfinding return; } - + if(!allowBypassObjects) return; diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp index 8886f5233..3f0acd1b2 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp @@ -14,8 +14,10 @@ namespace NKAI { namespace AIPathfinding { - AIMovementToDestinationRule::AIMovementToDestinationRule(std::shared_ptr nodeStorage) - : nodeStorage(nodeStorage) + AIMovementToDestinationRule::AIMovementToDestinationRule( + std::shared_ptr nodeStorage, + bool allowBypassObjects) + : nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects) { } @@ -37,15 +39,30 @@ namespace AIPathfinding return; } - if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->getAINode(source.node)->actor->allowBattle) + if(blocker == BlockingReason::SOURCE_GUARDED) { + auto actor = nodeStorage->getAINode(source.node)->actor; + + if(!allowBypassObjects) + { + if (source.node->getCost() == 0) + return; + + // when actor represents moster graph node, we need to let him escape monster + if(!destination.guarded && cb->getGuardingCreaturePosition(source.coord) == actor->initialPosition) + return; + } + + if(actor->allowBattle) + { #if NKAI_PATHFINDER_TRACE_LEVEL >= 1 - logAi->trace( - "Bypass src guard while moving from %s to %s", - source.coord.toString(), - destination.coord.toString()); + logAi->trace( + "Bypass src guard while moving from %s to %s", + source.coord.toString(), + destination.coord.toString()); #endif - return; + return; + } } destination.blocked = true; diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h index d33624514..a1c3015ac 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h @@ -24,9 +24,10 @@ namespace AIPathfinding { private: std::shared_ptr nodeStorage; + bool allowBypassObjects; public: - AIMovementToDestinationRule(std::shared_ptr nodeStorage); + AIMovementToDestinationRule(std::shared_ptr nodeStorage, bool allowBypassObjects); virtual void process( const PathNodeInfo & source, diff --git a/lib/logging/VisualLogger.cpp b/lib/logging/VisualLogger.cpp index a29fa40d4..ae0f2493b 100644 --- a/lib/logging/VisualLogger.cpp +++ b/lib/logging/VisualLogger.cpp @@ -21,7 +21,9 @@ void VisualLogger::updateWithLock(std::string channel, std::function Date: Sun, 11 Feb 2024 15:27:56 +0200 Subject: [PATCH 201/250] NKAI: water paths in graph --- AI/Nullkiller/AIGateway.cpp | 1 + AI/Nullkiller/Pathfinding/ObjectGraph.cpp | 48 ++++++++++++++++++++--- AI/Nullkiller/Pathfinding/ObjectGraph.h | 1 + Global.h | 14 +++++++ client/mapView/MapView.cpp | 5 ++- 5 files changed, 62 insertions(+), 7 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 9ad7c8421..947fb6c7e 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -373,6 +373,7 @@ void AIGateway::objectRemoved(const CGObjectInstance * obj, const PlayerColor & return; nullkiller->memory->removeFromMemory(obj); + nullkiller->baseGraph->removeObject(obj); if(obj->ID == Obj::HERO && obj->tempOwner == playerID) { diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp index 00d52f661..6182703f6 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "ObjectGraph.h" #include "AIPathfinderConfig.h" +#include "../../../lib/CRandomGenerator.h" #include "../../../CCallback.h" #include "../../../lib/mapping/CMap.h" #include "../Engine/Nullkiller.h" @@ -21,10 +22,10 @@ namespace NKAI void ObjectGraph::updateGraph(const Nullkiller * ai) { auto cb = ai->cb; - auto mapSize = cb->getMapSize(); std::map actors; std::map actorObjectMap; + std::vector boats; auto addObjectActor = [&](const CGObjectInstance * obj) { @@ -37,8 +38,14 @@ void ObjectGraph::updateGraph(const Nullkiller * ai) objectActor->pos = objectActor->convertFromVisitablePos(visitablePos); objectActor->initObj(rng); + if(cb->getTile(visitablePos)->isWater()) + { + boats.push_back(new CGBoat(objectActor->cb)); + objectActor->boat = boats.back(); + } + actorObjectMap[objectActor] = obj; - actors[objectActor] = obj->ID == Obj::TOWN ? HeroRole::MAIN : HeroRole::SCOUT; + actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD ? HeroRole::MAIN : HeroRole::SCOUT; addObject(obj); }; @@ -85,6 +92,17 @@ void ObjectGraph::updateGraph(const Nullkiller * ai) auto obj1 = actorObjectMap[path1.targetHero]; auto obj2 = actorObjectMap[path2.targetHero]; + auto tile1 = cb->getTile(obj1->visitablePos()); + auto tile2 = cb->getTile(obj2->visitablePos()); + + if(tile2->isWater() && !tile1->isWater()) + { + auto linkTile = cb->getTile(pos); + + if(!linkTile->isWater() || obj1->ID != Obj::BOAT || obj1->ID != Obj::SHIPYARD) + continue; + } + auto danger = ai->pathfinder->getStorage()->evaluateDanger(obj2->visitablePos(), path1.targetHero, true); auto updated = nodes[obj1->visitablePos()].connections[obj2->visitablePos()].update( @@ -108,11 +126,16 @@ void ObjectGraph::updateGraph(const Nullkiller * ai) } }); - for(auto h : actors) + for(auto h : actorObjectMap) { delete h.first; } + for(auto boat : boats) + { + delete boat; + } + #if NKAI_GRAPH_TRACE_LEVEL >= 1 dumpToLog("graph"); #endif @@ -123,6 +146,21 @@ void ObjectGraph::addObject(const CGObjectInstance * obj) nodes[obj->visitablePos()].init(obj); } +void ObjectGraph::removeObject(const CGObjectInstance * obj) +{ + nodes[obj->visitablePos()].objectExists = false; + + if(obj->ID == Obj::BOAT) + { + vstd::erase_if(nodes[obj->visitablePos()].connections, [&](const std::pair & link) -> bool + { + auto tile = cb->getTile(link.first, false); + + return tile && tile->isWater(); + }); + } +} + void ObjectGraph::connectHeroes(const Nullkiller * ai) { for(auto obj : ai->memory->visitableObjs) @@ -170,7 +208,7 @@ void ObjectGraph::dumpToLog(std::string visualKey) const node.second.danger); #endif - logBuilder.addLine(node.first, tile.first); + logBuilder.addLine(tile.first, node.first); } } }); @@ -206,9 +244,7 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil graph.iterateConnections(pos.coord, [&](int3 target, ObjectLink o) { - auto graphNode = graph.getNode(target); auto targetNodeType = o.danger ? GrapthPathNodeType::BATTLE : pos.nodeType; - auto targetPointer = GraphPathNodePointer(target, targetNodeType); auto & targetNode = getNode(targetPointer); diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.h b/AI/Nullkiller/Pathfinding/ObjectGraph.h index 11b9e84e3..faf6fb499 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraph.h +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.h @@ -60,6 +60,7 @@ public: void updateGraph(const Nullkiller * ai); void addObject(const CGObjectInstance * obj); void connectHeroes(const Nullkiller * ai); + void removeObject(const CGObjectInstance * obj); void dumpToLog(std::string visualKey) const; template diff --git a/Global.h b/Global.h index 3ef1a64e4..2b6b66be5 100644 --- a/Global.h +++ b/Global.h @@ -512,6 +512,20 @@ namespace vstd } } + //works for std::unordered_map, maybe something else + template + void erase_if(std::unordered_map & container, Predicate pred) + { + auto itr = container.begin(); + auto endItr = container.end(); + while(itr != endItr) + { + auto tmpItr = itr++; + if(pred(*tmpItr)) + container.erase(tmpItr); + } + } + template OutputIterator copy_if(const InputRange &input, OutputIterator result, Predicate pred) { diff --git a/client/mapView/MapView.cpp b/client/mapView/MapView.cpp index be6124b6b..008d28fc5 100644 --- a/client/mapView/MapView.cpp +++ b/client/mapView/MapView.cpp @@ -80,9 +80,12 @@ public: auto pEnd = model->getTargetTileArea(end).topLeft(); auto viewPort = target.getRenderArea(); + pStart.x += 3; + pEnd.x -= 3; + if(viewPort.isInside(pStart) && viewPort.isInside(pEnd)) { - target.drawLine(pStart, pEnd, ColorRGBA(255, 255, 0), ColorRGBA(255, 255, 0)); + target.drawLine(pStart, pEnd, ColorRGBA(255, 255, 0), ColorRGBA(255, 0, 0)); } } }; From 5dddb7ed00e1179033985805d08586ef1efde30b Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 2 Mar 2024 17:27:15 +0300 Subject: [PATCH 202/250] remove no longer used slot --- launcher/settingsView/csettingsview_moc.cpp | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 368ad48f0..fa5c97be6 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -397,25 +397,6 @@ void CSettingsView::on_comboBoxCursorType_currentIndexChanged(int index) node->String() = cursorTypesList[index]; } -void CSettingsView::on_listWidgetSettings_currentRowChanged(int currentRow) -{ - QVector targetWidgets = { - ui->labelGeneral, - ui->labelVideo, - ui->labelArtificialIntelligence, - ui->labelRepositories - }; - - QWidget * currentTarget = targetWidgets[currentRow]; - - // We want to scroll in a way that will put target widget in topmost visible position - // To show not just header, but all settings in this group as well - // In order to do that, let's scroll to the very bottom and the scroll back up until target widget is visible - int maxPosition = ui->settingsScrollArea->verticalScrollBar()->maximum(); - ui->settingsScrollArea->verticalScrollBar()->setValue(maxPosition); - ui->settingsScrollArea->ensureWidgetVisible(currentTarget, 5, 5); -} - void CSettingsView::loadTranslation() { Languages::fillLanguages(ui->comboBoxLanguageBase, true); From 1b69410bbc98d80be02ebdd828e17bb802af2dbb Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 2 Mar 2024 17:35:28 +0300 Subject: [PATCH 203/250] also remove the declaration from the header --- launcher/settingsView/csettingsview_moc.h | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/settingsView/csettingsview_moc.h b/launcher/settingsView/csettingsview_moc.h index 084c0bc50..eb557bd7e 100644 --- a/launcher/settingsView/csettingsview_moc.h +++ b/launcher/settingsView/csettingsview_moc.h @@ -45,7 +45,6 @@ private slots: void on_comboBoxAutoSave_currentIndexChanged(int index); void on_comboBoxLanguage_currentIndexChanged(int index); void on_comboBoxCursorType_currentIndexChanged(int index); - void on_listWidgetSettings_currentRowChanged(int currentRow); void on_pushButtonTranslation_clicked(); void on_comboBoxLanguageBase_currentIndexChanged(int index); From 5f8a157c6d20860e1e3a8339ccf4a09adbdc2edd Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 3 Mar 2024 10:21:17 +0200 Subject: [PATCH 204/250] NKAI: rollback changes with removing scan depth --- AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp | 4 ++-- AI/Nullkiller/Engine/Nullkiller.h | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index 74daae3a1..0096a466d 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -75,7 +75,7 @@ void DangerHitMapAnalyzer::updateHitMap() PathfinderSettings ps; - ps.scoutTurnDistanceLimit = ps.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT; + ps.scoutTurnDistanceLimit = ps.mainTurnDistanceLimit = ai->settings->getMainHeroTurnDistanceLimit(); ps.useHeroChain = false; ai->pathfinder->updatePaths(pair.second, ps); @@ -189,7 +189,7 @@ void DangerHitMapAnalyzer::calculateTileOwners() } PathfinderSettings ps; - ps.mainTurnDistanceLimit = ps.scoutTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT; + ps.mainTurnDistanceLimit = ps.scoutTurnDistanceLimit = ai->settings->getMainHeroTurnDistanceLimit(); ai->pathfinder->updatePaths(townHeroes, ps); diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 2f5588967..02508bdae 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -25,6 +25,7 @@ namespace NKAI { const float MIN_PRIORITY = 0.01f; +const float SMALL_SCAN_MIN_PRIORITY = 0.4f; enum class HeroLockedReason { @@ -37,6 +38,15 @@ enum class HeroLockedReason HERO_CHAIN = 3 }; +enum class ScanDepth +{ + MAIN_FULL = 0, + + SMALL = 1, + + ALL_FULL = 2 +}; + class Nullkiller { private: @@ -44,6 +54,7 @@ private: int3 targetTile; ObjectInstanceID targetObject; std::map lockedHeroes; + ScanDepth scanDepth; TResources lockedResources; bool useHeroChain; @@ -85,6 +96,7 @@ public: int32_t getFreeGold() const { return getFreeResources()[EGameResID::GOLD]; } void lockResources(const TResources & res); const TResources & getLockedResources() const { return lockedResources; } + ScanDepth getScanDepth() const { return scanDepth; } private: void resetAiState(); From 119fa1e96c2ea05fa9a84ceb4d755a6ba6106a25 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 4 Mar 2024 20:34:43 +0100 Subject: [PATCH 205/250] seperate path for ai vs human --- server/queries/BattleQueries.cpp | 15 +++++++++++++-- server/queries/BattleQueries.h | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/server/queries/BattleQueries.cpp b/server/queries/BattleQueries.cpp index 92e37fd6f..f97b0663f 100644 --- a/server/queries/BattleQueries.cpp +++ b/server/queries/BattleQueries.cpp @@ -23,6 +23,14 @@ #include "../../lib/networkPacks/PacksForServer.h" #include "../../lib/serializer/Cast.h" +void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const +{ + assert(result); + + if(result && !isAiVsHuman) + objectVisit.visitedObject->battleFinished(objectVisit.visitingHero, *result); +} + CBattleQuery::CBattleQuery(CGameHandler * owner, const IBattleInfo * bi): CQuery(owner), battleID(bi->getBattleID()) @@ -30,6 +38,8 @@ CBattleQuery::CBattleQuery(CGameHandler * owner, const IBattleInfo * bi): belligerents[0] = bi->getSideArmy(0); belligerents[1] = bi->getSideArmy(1); + isAiVsHuman = bi->getSidePlayer(1).isValidPlayer() && gh->getPlayerState(bi->getSidePlayer(0))->isHuman() != gh->getPlayerState(bi->getSidePlayer(1))->isHuman(); + addPlayer(bi->getSidePlayer(0)); addPlayer(bi->getSidePlayer(1)); } @@ -100,10 +110,11 @@ void CBattleDialogQuery::onRemoval(PlayerColor color) { auto hero = bi->getSideHero(BattleSide::ATTACKER); auto visitingObj = bi->getDefendedTown() ? bi->getDefendedTown() : gh->getVisitingObject(hero); + bool isAiVsHuman = bi->getSidePlayer(1).isValidPlayer() && gh->getPlayerState(bi->getSidePlayer(0))->isHuman() != gh->getPlayerState(bi->getSidePlayer(1))->isHuman(); gh->battles->endBattleConfirm(bi->getBattleID()); - - if(visitingObj) + + if(visitingObj && result && isAiVsHuman) visitingObj->battleFinished(hero, *result); } } diff --git a/server/queries/BattleQueries.h b/server/queries/BattleQueries.h index 41d92ec98..84064a81d 100644 --- a/server/queries/BattleQueries.h +++ b/server/queries/BattleQueries.h @@ -23,11 +23,13 @@ public: std::array belligerents; std::array initialHeroMana; + bool isAiVsHuman; BattleID battleID; std::optional result; CBattleQuery(CGameHandler * owner); CBattleQuery(CGameHandler * owner, const IBattleInfo * Bi); //TODO + void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override; bool blocksPack(const CPack *pack) const override; void onRemoval(PlayerColor color) override; void onExposure(QueryPtr topQuery) override; From e7af9d56072067383c8af325b596059fbe3b2df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 4 Mar 2024 21:23:17 +0100 Subject: [PATCH 206/250] Code review fixes, Sonarcloud fixes --- client/CServerHandler.cpp | 5 +---- client/lobby/RandomMapTab.cpp | 12 +++++------- client/lobby/SelectionTab.cpp | 6 +++++- mapeditor/jsonutils.cpp | 4 +++- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 4cba861a4..93d139bc8 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -196,10 +196,7 @@ void CServerHandler::startLocalServerAndConnect(bool connectToLobby) auto si = std::make_shared(); auto lastDifficulty = settings["general"]["lastDifficulty"]; - if (lastDifficulty.isNumber()) - { - si->difficulty = lastDifficulty.Integer(); - } + si->difficulty = lastDifficulty.Integer(); logNetwork->trace("\tStarting local server"); serverRunner->start(getLocalPort(), connectToLobby, si); diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index a83d30757..428973e02 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -38,7 +38,6 @@ #include "../../lib/filesystem/Filesystem.h" #include "../../lib/RoadHandler.h" -//#include "../../lib/GameSettings.h" #include "../../lib/CConfigHandler.h" #include "../../lib/serializer/JsonSerializer.h" #include "../../lib/serializer/JsonDeserializer.h" @@ -168,7 +167,6 @@ RandomMapTab::RandomMapTab(): } loadOptions(); - //updateMapInfoByHost(); } void RandomMapTab::updateMapInfoByHost() @@ -467,7 +465,7 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab): //int totalPlayers = randomMapTab.obtainMapGenOptions().getPlayerLimit(); int totalPlayers = randomMapTab.obtainMapGenOptions().getMaxPlayersCount(); assert(totalPlayers <= PlayerColor::PLAYER_LIMIT_I); - auto settings = randomMapTab.obtainMapGenOptions().getPlayersSettings(); + auto playerSettings = randomMapTab.obtainMapGenOptions().getPlayersSettings(); variables["totalPlayers"].Integer() = totalPlayers; pos.w = variables["windowSize"]["x"].Integer() + totalPlayers * variables["cellMargin"]["x"].Integer(); @@ -508,20 +506,20 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab): // Window should have X * X columns, where X is max players allowed for current settings // For random player count, X is 8 - if (totalPlayers > settings.size()) + if (totalPlayers > playerSettings.size()) { auto savedPlayers = randomMapTab.obtainMapGenOptions().getSavedPlayersMap(); for (const auto & player : savedPlayers) { - if (!vstd::contains(settings, player.first)) + if (!vstd::contains(playerSettings, player.first)) { - settings[player.first] = player.second; + playerSettings[player.first] = player.second; } } } std::vector settingsVec; - for (const auto & player : settings) + for (const auto & player : playerSettings) { settingsVec.push_back(player.second); } diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 55858e72c..938bf8fff 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -168,7 +168,11 @@ SelectionTab::SelectionTab(ESelectionScreen Type) labelMapSizes = std::make_shared(87, 62, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[510]); // TODO: Global constants? - int sizes[] = {36, 72, 108, 144, 0}; + int sizes[] = {CMapHeader::MAP_SIZE_SMALL, + CMapHeader::MAP_SIZE_MIDDLE, + CMapHeader::MAP_SIZE_LARGE, + CMapHeader::MAP_SIZE_XLARGE, + 0}; const char * filterIconNmes[] = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"}; for(int i = 0; i < 5; i++) buttonsSortBy.push_back(std::make_shared(Point(158 + 47 * i, 46), AnimationPath::builtin(filterIconNmes[i]), CGI->generaltexth->zelp[54 + i], std::bind(&SelectionTab::filter, this, sizes[i], true))); diff --git a/mapeditor/jsonutils.cpp b/mapeditor/jsonutils.cpp index f7ce677bb..f294db28a 100644 --- a/mapeditor/jsonutils.cpp +++ b/mapeditor/jsonutils.cpp @@ -69,9 +69,11 @@ QVariant toVariant(const JsonNode & node) return QVariant(node.Bool()); break; case JsonNode::JsonType::DATA_FLOAT: - case JsonNode::JsonType::DATA_INTEGER: return QVariant(node.Float()); break; + case JsonNode::JsonType::DATA_INTEGER: + return QVariant(node.Integer()); + break; case JsonNode::JsonType::DATA_STRING: return QVariant(QString::fromUtf8(node.String().c_str())); break; From 5ca3fd2d9cf968812af5bdbcde0a3d71063a9e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 4 Mar 2024 21:49:35 +0100 Subject: [PATCH 207/250] Restored previous solution, which is handled already. --- mapeditor/jsonutils.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mapeditor/jsonutils.cpp b/mapeditor/jsonutils.cpp index f294db28a..f7ce677bb 100644 --- a/mapeditor/jsonutils.cpp +++ b/mapeditor/jsonutils.cpp @@ -69,10 +69,8 @@ QVariant toVariant(const JsonNode & node) return QVariant(node.Bool()); break; case JsonNode::JsonType::DATA_FLOAT: - return QVariant(node.Float()); - break; case JsonNode::JsonType::DATA_INTEGER: - return QVariant(node.Integer()); + return QVariant(node.Float()); break; case JsonNode::JsonType::DATA_STRING: return QVariant(QString::fromUtf8(node.String().c_str())); From d143f53d7eecea7446bd5c728cdb51d63c2d374c Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Tue, 5 Mar 2024 17:59:56 +0200 Subject: [PATCH 208/250] using deque for hero's backpack storage --- client/widgets/CArtifactsOfHeroAltar.cpp | 2 +- client/widgets/CArtifactsOfHeroBackpack.cpp | 1 + client/widgets/CArtifactsOfHeroBackpack.h | 1 + client/widgets/CArtifactsOfHeroBase.cpp | 77 ++++++++------------- client/widgets/CArtifactsOfHeroBase.h | 5 +- client/widgets/CArtifactsOfHeroMarket.cpp | 2 +- lib/CArtHandler.h | 2 +- lib/serializer/BinaryDeserializer.h | 9 +++ lib/serializer/BinarySerializer.h | 8 +++ 9 files changed, 54 insertions(+), 53 deletions(-) diff --git a/client/widgets/CArtifactsOfHeroAltar.cpp b/client/widgets/CArtifactsOfHeroAltar.cpp index 8011d8bf1..2b8dee7ba 100644 --- a/client/widgets/CArtifactsOfHeroAltar.cpp +++ b/client/widgets/CArtifactsOfHeroAltar.cpp @@ -25,7 +25,7 @@ CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position) std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2), std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2), position, - std::bind(&CArtifactsOfHeroAltar::scrollBackpack, this, _1)); + std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1)); // The backpack is in the altar window above and to the right for(auto & slot : backpack) diff --git a/client/widgets/CArtifactsOfHeroBackpack.cpp b/client/widgets/CArtifactsOfHeroBackpack.cpp index ddc75c544..e3a54c647 100644 --- a/client/widgets/CArtifactsOfHeroBackpack.cpp +++ b/client/widgets/CArtifactsOfHeroBackpack.cpp @@ -26,6 +26,7 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(size_t slotsColumnsMax, size_t slotsRowsMax) : slotsColumnsMax(slotsColumnsMax) , slotsRowsMax(slotsRowsMax) + , backpackPos(0) { setRedrawParent(true); } diff --git a/client/widgets/CArtifactsOfHeroBackpack.h b/client/widgets/CArtifactsOfHeroBackpack.h index d79776109..59457862d 100644 --- a/client/widgets/CArtifactsOfHeroBackpack.h +++ b/client/widgets/CArtifactsOfHeroBackpack.h @@ -36,6 +36,7 @@ protected: size_t slotsRowsMax; const int slotSizeWithMargin = 46; const int sliderPosOffsetX = 5; + int backpackPos; // Position to display artifacts in heroes backpack void initAOHbackpack(size_t slots, bool slider); size_t calcRows(size_t slots); diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index 924830ce4..920317747 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -25,8 +25,7 @@ #include "../../lib/networkPacks/ArtifactLocation.h" CArtifactsOfHeroBase::CArtifactsOfHeroBase() - : backpackPos(0), - curHero(nullptr), + : curHero(nullptr), putBackPickedArtCallback(nullptr) { } @@ -56,10 +55,10 @@ void CArtifactsOfHeroBase::setPutBackPickedArtifactCallback(PutBackPickedArtCall } void CArtifactsOfHeroBase::init( - CArtPlace::ClickFunctor lClickCallback, - CArtPlace::ClickFunctor showPopupCallback, + const CArtPlace::ClickFunctor & lClickCallback, + const CArtPlace::ClickFunctor & showPopupCallback, const Point & position, - BpackScrollFunctor scrollCallback) + const BpackScrollFunctor & scrollCallback) { // CArtifactsOfHeroBase::init may be transform to CArtifactsOfHeroBase::CArtifactsOfHeroBase if OBJECT_CONSTRUCTION_CAPTURING is removed OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE); @@ -87,8 +86,10 @@ void CArtifactsOfHeroBase::init( artPlace->setClickPressedCallback(lClickCallback); artPlace->setShowPopupCallback(showPopupCallback); } - leftBackpackRoll = std::make_shared(Point(379, 364), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(), [scrollCallback]() {scrollCallback(-1);}, EShortcut::MOVE_LEFT); - rightBackpackRoll = std::make_shared(Point(632, 364), AnimationPath::builtin("hsbtns5.def"), CButton::tooltip(), [scrollCallback]() {scrollCallback(+1);}, EShortcut::MOVE_RIGHT); + leftBackpackRoll = std::make_shared(Point(379, 364), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(), + [scrollCallback](){scrollCallback(-1);}, EShortcut::MOVE_LEFT); + rightBackpackRoll = std::make_shared(Point(632, 364), AnimationPath::builtin("hsbtns5.def"), CButton::tooltip(), + [scrollCallback](){scrollCallback(+1);}, EShortcut::MOVE_RIGHT); leftBackpackRoll->block(true); rightBackpackRoll->block(true); @@ -116,16 +117,12 @@ void CArtifactsOfHeroBase::gestureArtPlace(CArtPlace & artPlace, const Point & c void CArtifactsOfHeroBase::setHero(const CGHeroInstance * hero) { curHero = hero; - if(curHero->artifactsInBackpack.size() > 0) - backpackPos %= curHero->artifactsInBackpack.size(); - else - backpackPos = 0; for(auto slot : artWorn) { setSlotData(slot.second, slot.first); } - scrollBackpack(0); + updateBackpackSlots(); } const CGHeroInstance * CArtifactsOfHeroBase::getHero() const @@ -135,46 +132,28 @@ const CGHeroInstance * CArtifactsOfHeroBase::getHero() const void CArtifactsOfHeroBase::scrollBackpack(int offset) { - // offset==-1 => to left; offset==1 => to right - using slotInc = std::function; - auto artsInBackpack = static_cast(curHero->artifactsInBackpack.size()); - auto scrollingPossible = artsInBackpack > backpack.size(); - - slotInc inc_straight = [](ArtifactPosition & slot) -> ArtifactPosition + const ArtifactLocation beginLoc = ArtifactLocation(curHero->id, ArtifactPosition::BACKPACK_START); + const ArtifactLocation endLoc = ArtifactLocation(curHero->id, ArtifactPosition(ArtifactPosition::BACKPACK_START + curHero->artifactsInBackpack.size() - 1)); + // To right by default + ArtifactLocation const * srcLoc = &beginLoc; + ArtifactLocation const * dstLoc = &endLoc; + if(offset < 0) { - return slot + 1; - }; - slotInc inc_ring = [artsInBackpack](ArtifactPosition & slot) -> ArtifactPosition - { - return ArtifactPosition::BACKPACK_START + (slot - ArtifactPosition::BACKPACK_START + 1) % artsInBackpack; - }; - slotInc inc; - if(scrollingPossible) - inc = inc_ring; - else - inc = inc_straight; + // To left + srcLoc = &endLoc; + dstLoc = &beginLoc; + offset = -offset; + } - backpackPos += offset; - if(backpackPos < 0) - backpackPos += artsInBackpack; + for(auto step = 0; step < offset; step++) + LOCPLINT->cb->swapArtifacts(*srcLoc, *dstLoc); - if(artsInBackpack) - backpackPos %= artsInBackpack; - - auto slot = ArtifactPosition(ArtifactPosition::BACKPACK_START + backpackPos); + ArtifactPosition slot = ArtifactPosition::BACKPACK_START; for(auto artPlace : backpack) { setSlotData(artPlace, slot); - slot = inc(slot); + slot = slot + 1; } - - // Blocking scrolling if there is not enough artifacts to scroll - if(leftBackpackRoll) - leftBackpackRoll->block(!scrollingPossible); - if(rightBackpackRoll) - rightBackpackRoll->block(!scrollingPossible); - - redraw(); } void CArtifactsOfHeroBase::markPossibleSlots(const CArtifactInstance * art, bool assumeDestRemoved) @@ -224,9 +203,13 @@ void CArtifactsOfHeroBase::updateWornSlots() void CArtifactsOfHeroBase::updateBackpackSlots() { - if(curHero->artifactsInBackpack.size() <= backpack.size() && backpackPos != 0) - backpackPos = 0; scrollBackpack(0); + auto scrollingPossible = static_cast(curHero->artifactsInBackpack.size()) > backpack.size(); + // Blocking scrolling if there is not enough artifacts to scroll + if(leftBackpackRoll) + leftBackpackRoll->block(!scrollingPossible); + if(rightBackpackRoll) + rightBackpackRoll->block(!scrollingPossible); } void CArtifactsOfHeroBase::updateSlot(const ArtifactPosition & slot) diff --git a/client/widgets/CArtifactsOfHeroBase.h b/client/widgets/CArtifactsOfHeroBase.h index 83dbc8389..b1296c44a 100644 --- a/client/widgets/CArtifactsOfHeroBase.h +++ b/client/widgets/CArtifactsOfHeroBase.h @@ -52,7 +52,6 @@ protected: std::vector backpack; std::shared_ptr leftBackpackRoll; std::shared_ptr rightBackpackRoll; - int backpackPos; // Position to display artifacts in heroes backpack PutBackPickedArtCallback putBackPickedArtCallback; const std::vector slotPos = @@ -66,8 +65,8 @@ protected: Point(381,295) //18 }; - virtual void init(CHeroArtPlace::ClickFunctor lClickCallback, CHeroArtPlace::ClickFunctor showPopupCallback, - const Point & position, BpackScrollFunctor scrollCallback); + virtual void init(const CHeroArtPlace::ClickFunctor & lClickCallback, const CHeroArtPlace::ClickFunctor & showPopupCallback, + const Point & position, const BpackScrollFunctor & scrollCallback); // Assigns an artifacts to an artifact place depending on it's new slot ID virtual void setSlotData(ArtPlacePtr artPlace, const ArtifactPosition & slot); }; diff --git a/client/widgets/CArtifactsOfHeroMarket.cpp b/client/widgets/CArtifactsOfHeroMarket.cpp index 257c212b9..e2e7da251 100644 --- a/client/widgets/CArtifactsOfHeroMarket.cpp +++ b/client/widgets/CArtifactsOfHeroMarket.cpp @@ -18,7 +18,7 @@ CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position) std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2), std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2), position, - std::bind(&CArtifactsOfHeroMarket::scrollBackpack, this, _1)); + std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1)); for(const auto & [slot, artPlace] : artWorn) artPlace->setSelectionWidth(2); diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index a4c416028..904aa44bd 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -192,7 +192,7 @@ class DLL_LINKAGE CArtifactSet public: using ArtPlacementMap = std::map; - std::vector artifactsInBackpack; //hero's artifacts from bag + std::deque artifactsInBackpack; //hero's artifacts from bag std::map artifactsWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 std::vector artifactsTransitionPos; // Used as transition position for dragAndDrop artifact exchange diff --git a/lib/serializer/BinaryDeserializer.h b/lib/serializer/BinaryDeserializer.h index 5f702e190..162c0f6c3 100644 --- a/lib/serializer/BinaryDeserializer.h +++ b/lib/serializer/BinaryDeserializer.h @@ -218,6 +218,15 @@ public: load( data[i]); } + template , int > = 0> + void load(std::deque & data) + { + ui32 length = readAndCheckLength(); + data.resize(length); + for(ui32 i = 0; i < length; i++) + load(data[i]); + } + template < typename T, typename std::enable_if_t < std::is_pointer_v, int > = 0 > void load(T &data) { diff --git a/lib/serializer/BinarySerializer.h b/lib/serializer/BinarySerializer.h index 28da150eb..0ee73e669 100644 --- a/lib/serializer/BinarySerializer.h +++ b/lib/serializer/BinarySerializer.h @@ -276,6 +276,14 @@ public: for(ui32 i=0;i, int > = 0> + void save(const std::deque & data) + { + ui32 length = (ui32)data.size(); + *this & length; + for(ui32 i = 0; i < length; i++) + save(data[i]); + } template void save(const std::array &data) { From b1f52eec41daae1f7f5ad92f0e45c073ce8bb06a Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:16:35 +0200 Subject: [PATCH 209/250] ManageBackpackArtifacts --- CCallback.cpp | 8 +++++ CCallback.h | 2 ++ client/NetPacksClient.cpp | 2 +- client/widgets/CArtifactsOfHeroBackpack.h | 2 +- client/widgets/CArtifactsOfHeroBase.cpp | 36 ++++++-------------- client/widgets/CArtifactsOfHeroBase.h | 2 +- client/widgets/CArtifactsOfHeroMarket.cpp | 4 +-- client/widgets/CArtifactsOfHeroMarket.h | 2 +- client/widgets/CWindowWithArtifacts.cpp | 6 +++- lib/networkPacks/NetPackVisitor.h | 1 + lib/networkPacks/NetPacksLib.cpp | 5 +++ lib/networkPacks/PacksForServer.h | 27 +++++++++++++++ lib/registerTypes/RegisterTypesServerPacks.h | 1 + server/CGameHandler.cpp | 19 +++++++++++ server/CGameHandler.h | 1 + server/NetPacksServer.cpp | 21 ++++++++++++ server/ServerNetPackVisitors.h | 1 + server/queries/MapQueries.cpp | 3 ++ 18 files changed, 110 insertions(+), 33 deletions(-) diff --git a/CCallback.cpp b/CCallback.cpp index 3b692ec21..b892bb8ab 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -180,6 +180,14 @@ void CCallback::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dst sendRequest(&bma); } +void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left) +{ + ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SCROLL_RIGHT); + if(left) + mba.cmd = ManageBackpackArtifacts::ManageCmd::SCROLL_LEFT; + sendRequest(&mba); +} + void CCallback::eraseArtifactByClient(const ArtifactLocation & al) { EraseArtifactByClient ea(al); diff --git a/CCallback.h b/CCallback.h index 5e9f1cf1a..091371e3f 100644 --- a/CCallback.h +++ b/CCallback.h @@ -90,6 +90,7 @@ public: virtual int splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val)=0;//split creatures from the first stack //virtual bool swapArtifacts(const CGHeroInstance * hero1, ui16 pos1, const CGHeroInstance * hero2, ui16 pos2)=0; //swaps artifacts between two given heroes virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0; + virtual void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) = 0; virtual void assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0; virtual void eraseArtifactByClient(const ArtifactLocation & al)=0; virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0; @@ -174,6 +175,7 @@ public: bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2) override; void assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override; void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped = true, bool backpack = true) override; + void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) override; void eraseArtifactByClient(const ArtifactLocation & al) override; bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override; void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override; diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index ff6c037b1..a6cbec495 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -290,7 +290,7 @@ void ApplyClientNetPackVisitor::visitMoveArtifact(MoveArtifact & pack) callInterfaceIfPresent(cl, player, &IGameEventsReceiver::askToAssembleArtifact, pack.dst); }; - moveArtifact(cl.getOwner(pack.src.artHolder)); + moveArtifact(LOCPLINT->playerID); if(cl.getOwner(pack.src.artHolder) != cl.getOwner(pack.dst.artHolder)) moveArtifact(cl.getOwner(pack.dst.artHolder)); diff --git a/client/widgets/CArtifactsOfHeroBackpack.h b/client/widgets/CArtifactsOfHeroBackpack.h index 59457862d..4a53bf790 100644 --- a/client/widgets/CArtifactsOfHeroBackpack.h +++ b/client/widgets/CArtifactsOfHeroBackpack.h @@ -24,7 +24,7 @@ class CArtifactsOfHeroBackpack : public CArtifactsOfHeroBase public: CArtifactsOfHeroBackpack(size_t slotsColumnsMax, size_t slotsRowsMax); CArtifactsOfHeroBackpack(); - void scrollBackpack(int offset) override; + void scrollBackpack(int offset); void updateBackpackSlots() override; size_t getActiveSlotRowsNum(); size_t getSlotsNum(); diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index 920317747..f1e406571 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -87,9 +87,9 @@ void CArtifactsOfHeroBase::init( artPlace->setShowPopupCallback(showPopupCallback); } leftBackpackRoll = std::make_shared(Point(379, 364), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(), - [scrollCallback](){scrollCallback(-1);}, EShortcut::MOVE_LEFT); + [scrollCallback](){scrollCallback(true);}, EShortcut::MOVE_LEFT); rightBackpackRoll = std::make_shared(Point(632, 364), AnimationPath::builtin("hsbtns5.def"), CButton::tooltip(), - [scrollCallback](){scrollCallback(+1);}, EShortcut::MOVE_RIGHT); + [scrollCallback](){scrollCallback(false);}, EShortcut::MOVE_RIGHT); leftBackpackRoll->block(true); rightBackpackRoll->block(true); @@ -130,30 +130,9 @@ const CGHeroInstance * CArtifactsOfHeroBase::getHero() const return curHero; } -void CArtifactsOfHeroBase::scrollBackpack(int offset) +void CArtifactsOfHeroBase::scrollBackpack(bool left) { - const ArtifactLocation beginLoc = ArtifactLocation(curHero->id, ArtifactPosition::BACKPACK_START); - const ArtifactLocation endLoc = ArtifactLocation(curHero->id, ArtifactPosition(ArtifactPosition::BACKPACK_START + curHero->artifactsInBackpack.size() - 1)); - // To right by default - ArtifactLocation const * srcLoc = &beginLoc; - ArtifactLocation const * dstLoc = &endLoc; - if(offset < 0) - { - // To left - srcLoc = &endLoc; - dstLoc = &beginLoc; - offset = -offset; - } - - for(auto step = 0; step < offset; step++) - LOCPLINT->cb->swapArtifacts(*srcLoc, *dstLoc); - - ArtifactPosition slot = ArtifactPosition::BACKPACK_START; - for(auto artPlace : backpack) - { - setSlotData(artPlace, slot); - slot = slot + 1; - } + LOCPLINT->cb->scrollBackpackArtifacts(curHero->id, left); } void CArtifactsOfHeroBase::markPossibleSlots(const CArtifactInstance * art, bool assumeDestRemoved) @@ -203,7 +182,12 @@ void CArtifactsOfHeroBase::updateWornSlots() void CArtifactsOfHeroBase::updateBackpackSlots() { - scrollBackpack(0); + ArtifactPosition slot = ArtifactPosition::BACKPACK_START; + for(auto & artPlace : backpack) + { + setSlotData(artPlace, slot); + slot = slot + 1; + } auto scrollingPossible = static_cast(curHero->artifactsInBackpack.size()) > backpack.size(); // Blocking scrolling if there is not enough artifacts to scroll if(leftBackpackRoll) diff --git a/client/widgets/CArtifactsOfHeroBase.h b/client/widgets/CArtifactsOfHeroBase.h index b1296c44a..9a219000a 100644 --- a/client/widgets/CArtifactsOfHeroBase.h +++ b/client/widgets/CArtifactsOfHeroBase.h @@ -36,7 +36,7 @@ public: virtual void gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition); virtual void setHero(const CGHeroInstance * hero); virtual const CGHeroInstance * getHero() const; - virtual void scrollBackpack(int offset); + virtual void scrollBackpack(bool left); virtual void markPossibleSlots(const CArtifactInstance * art, bool assumeDestRemoved = true); virtual void unmarkSlots(); virtual ArtPlacePtr getArtPlace(const ArtifactPosition & slot); diff --git a/client/widgets/CArtifactsOfHeroMarket.cpp b/client/widgets/CArtifactsOfHeroMarket.cpp index e2e7da251..3fccd62de 100644 --- a/client/widgets/CArtifactsOfHeroMarket.cpp +++ b/client/widgets/CArtifactsOfHeroMarket.cpp @@ -26,9 +26,9 @@ CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position) artPlace->setSelectionWidth(2); }; -void CArtifactsOfHeroMarket::scrollBackpack(int offset) +void CArtifactsOfHeroMarket::scrollBackpack(bool left) { - CArtifactsOfHeroBase::scrollBackpack(offset); + CArtifactsOfHeroBase::scrollBackpack(left); // We may have highlight on one of backpack artifacts if(selectArtCallback) diff --git a/client/widgets/CArtifactsOfHeroMarket.h b/client/widgets/CArtifactsOfHeroMarket.h index c8d34c399..68aee92e0 100644 --- a/client/widgets/CArtifactsOfHeroMarket.h +++ b/client/widgets/CArtifactsOfHeroMarket.h @@ -17,5 +17,5 @@ public: std::function selectArtCallback; CArtifactsOfHeroMarket(const Point & position); - void scrollBackpack(int offset) override; + void scrollBackpack(bool left) override; }; diff --git a/client/widgets/CWindowWithArtifacts.cpp b/client/widgets/CWindowWithArtifacts.cpp index af3894604..7d6979f5c 100644 --- a/client/widgets/CWindowWithArtifacts.cpp +++ b/client/widgets/CWindowWithArtifacts.cpp @@ -145,7 +145,11 @@ void CWindowWithArtifacts::clickPressedArtPlaceHero(CArtifactsOfHeroBase & artsI if(artSetPtr->getHero()->getOwner() == LOCPLINT->playerID) { if(checkSpecialArts(*art, hero, std::is_same_v> ? true : false)) - LOCPLINT->cb->swapArtifacts(ArtifactLocation(artSetPtr->getHero()->id, artPlace.slot), ArtifactLocation(artSetPtr->getHero()->id, ArtifactPosition::TRANSITION_POS)); + { + assert(artSetPtr->getHero()->getSlotByInstance(art)); + LOCPLINT->cb->swapArtifacts(ArtifactLocation(artSetPtr->getHero()->id, artSetPtr->getHero()->getSlotByInstance(art)), + ArtifactLocation(artSetPtr->getHero()->id, ArtifactPosition::TRANSITION_POS)); + } } else { diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index fe1d9181e..a36e0d703 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -131,6 +131,7 @@ public: virtual void visitGarrisonHeroSwap(GarrisonHeroSwap & pack) {} virtual void visitExchangeArtifacts(ExchangeArtifacts & pack) {} virtual void visitBulkExchangeArtifacts(BulkExchangeArtifacts & pack) {} + virtual void visitManageBackpackArtifacts(ManageBackpackArtifacts & pack) {} virtual void visitAssembleArtifacts(AssembleArtifacts & pack) {} virtual void visitEraseArtifactByClient(EraseArtifactByClient & pack) {} virtual void visitBuyArtifact(BuyArtifact & pack) {} diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 00ed57775..752aed618 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -608,6 +608,11 @@ void BulkExchangeArtifacts::visitTyped(ICPackVisitor & visitor) visitor.visitBulkExchangeArtifacts(*this); } +void ManageBackpackArtifacts::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitManageBackpackArtifacts(*this); +} + void AssembleArtifacts::visitTyped(ICPackVisitor & visitor) { visitor.visitAssembleArtifacts(*this); diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index 14a60cce3..0c86a9769 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -401,6 +401,33 @@ struct DLL_LINKAGE BulkExchangeArtifacts : public CPackForServer } }; +struct DLL_LINKAGE ManageBackpackArtifacts : public CPackForServer +{ + enum class ManageCmd + { + SCROLL_LEFT, SCROLL_RIGHT, SORT_BY_SLOT, SORT_BY_CLASS, SORT_BY_COST + }; + + ManageBackpackArtifacts() = default; + ManageBackpackArtifacts(const ObjectInstanceID & artHolder, const ManageCmd & cmd) + : artHolder(artHolder) + , cmd(cmd) + { + } + + ObjectInstanceID artHolder; + ManageCmd cmd; + + void visitTyped(ICPackVisitor & visitor) override; + + template void serialize(Handler & h) + { + h & static_cast(*this); + h & artHolder; + h & cmd; + } +}; + struct DLL_LINKAGE AssembleArtifacts : public CPackForServer { AssembleArtifacts() = default; diff --git a/lib/registerTypes/RegisterTypesServerPacks.h b/lib/registerTypes/RegisterTypesServerPacks.h index f5eed0156..b0c3ae181 100644 --- a/lib/registerTypes/RegisterTypesServerPacks.h +++ b/lib/registerTypes/RegisterTypesServerPacks.h @@ -49,6 +49,7 @@ void registerTypesServerPacks(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType < CPackForServer, ManageBackpackArtifacts>(); s.template registerType(); s.template registerType(); } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 2cb20219b..9ee08fb8b 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2857,6 +2857,25 @@ bool CGameHandler::bulkMoveArtifacts(ObjectInstanceID srcId, ObjectInstanceID ds return true; } +bool CGameHandler::scrollBackpackArtifacts(const ObjectInstanceID heroID, bool left) +{ + auto artSet = getArtSet(heroID); + COMPLAIN_RET_FALSE_IF(artSet == nullptr, "scrollBackpackArtifacts: wrong hero's ID"); + + BulkMoveArtifacts bma(heroID, heroID, false); + + const auto backpackEnd = ArtifactPosition(ArtifactPosition::BACKPACK_START + artSet->artifactsInBackpack.size() - 1); + if(backpackEnd > ArtifactPosition::BACKPACK_START) + { + if(left) + bma.artsPack0.push_back(BulkMoveArtifacts::LinkedSlots(backpackEnd, ArtifactPosition::BACKPACK_START)); + else + bma.artsPack0.push_back(BulkMoveArtifacts::LinkedSlots(ArtifactPosition::BACKPACK_START, backpackEnd)); + sendAndApply(&bma); + } + return true; +} + /** * Assembles or disassembles a combination artifact. * @param heroID ID of hero holding the artifact(s). diff --git a/server/CGameHandler.h b/server/CGameHandler.h index dc6bdd3e1..31b302082 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -130,6 +130,7 @@ public: void removeArtifact(const ArtifactLocation &al) override; bool moveArtifact(const ArtifactLocation & src, const ArtifactLocation & dst) override; bool bulkMoveArtifacts(ObjectInstanceID srcId, ObjectInstanceID dstId, bool swap, bool equipped, bool backpack); + bool scrollBackpackArtifacts(const ObjectInstanceID heroID, bool left); bool eraseArtifactByClient(const ArtifactLocation & al); void synchronizeArtifactHandlerLists(); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 6ad658784..8cadd54cd 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -148,6 +148,27 @@ void ApplyGhNetPackVisitor::visitBulkExchangeArtifacts(BulkExchangeArtifacts & p result = gh.bulkMoveArtifacts(pack.srcHero, pack.dstHero, pack.swap, pack.equipped, pack.backpack); } +void ApplyGhNetPackVisitor::visitManageBackpackArtifacts(ManageBackpackArtifacts & pack) +{ + if(gh.getPlayerRelations(pack.player, gh.getOwner(pack.artHolder)) != PlayerRelations::ENEMIES) + { + if(pack.cmd == ManageBackpackArtifacts::ManageCmd::SCROLL_LEFT) + result = gh.scrollBackpackArtifacts(pack.artHolder, true); + else if(pack.cmd == ManageBackpackArtifacts::ManageCmd::SCROLL_RIGHT) + result = gh.scrollBackpackArtifacts(pack.artHolder, false); + else + { + gh.throwIfWrongOwner(&pack, pack.artHolder); + if(pack.cmd == ManageBackpackArtifacts::ManageCmd::SORT_BY_CLASS) + result = true; + else if(pack.cmd == ManageBackpackArtifacts::ManageCmd::SORT_BY_COST) + result = true; + else if(pack.cmd == ManageBackpackArtifacts::ManageCmd::SORT_BY_SLOT) + result = true; + } + } +} + void ApplyGhNetPackVisitor::visitAssembleArtifacts(AssembleArtifacts & pack) { gh.throwIfWrongOwner(&pack, pack.heroID); diff --git a/server/ServerNetPackVisitors.h b/server/ServerNetPackVisitors.h index c2cc0de42..a56395d7e 100644 --- a/server/ServerNetPackVisitors.h +++ b/server/ServerNetPackVisitors.h @@ -46,6 +46,7 @@ public: void visitGarrisonHeroSwap(GarrisonHeroSwap & pack) override; void visitExchangeArtifacts(ExchangeArtifacts & pack) override; void visitBulkExchangeArtifacts(BulkExchangeArtifacts & pack) override; + void visitManageBackpackArtifacts(ManageBackpackArtifacts & pack) override; void visitAssembleArtifacts(AssembleArtifacts & pack) override; void visitEraseArtifactByClient(EraseArtifactByClient & pack) override; void visitBuyArtifact(BuyArtifact & pack) override; diff --git a/server/queries/MapQueries.cpp b/server/queries/MapQueries.cpp index 424404fe8..69b25d9c7 100644 --- a/server/queries/MapQueries.cpp +++ b/server/queries/MapQueries.cpp @@ -128,6 +128,9 @@ bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const if(auto arts = dynamic_ptr_cast(pack)) return !vstd::contains(ourIds, arts->srcHero) || !vstd::contains(ourIds, arts->dstHero); + if(auto arts = dynamic_ptr_cast(pack)) + return !vstd::contains(ourIds, arts->artHolder); + if(auto art = dynamic_ptr_cast(pack)) { if(auto id = art->al.artHolder) From 9f688e6fb793aea58fd99acf7b9412fea787c2b3 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:52:50 +0200 Subject: [PATCH 210/250] MoveArtifact, BulkMoveArtifacts PlayerColor player field --- client/Client.h | 2 +- client/NetPacksClient.cpp | 6 +++--- client/widgets/CArtifactsOfHeroBackpack.cpp | 6 +++--- client/widgets/CArtifactsOfHeroBackpack.h | 2 +- lib/IGameCallback.h | 2 +- lib/networkPacks/PacksForClient.h | 16 +++++++++++----- server/CGameHandler.cpp | 12 ++++++------ server/CGameHandler.h | 6 +++--- server/NetPacksServer.cpp | 8 ++++---- server/queries/MapQueries.cpp | 3 +++ test/mock/mock_IGameCallback.h | 2 +- 11 files changed, 37 insertions(+), 28 deletions(-) diff --git a/client/Client.h b/client/Client.h index 5fe64b6b1..9d700b4dc 100644 --- a/client/Client.h +++ b/client/Client.h @@ -191,7 +191,7 @@ public: bool giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * artType, ArtifactPosition pos) override {return false;} bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional askAssemble) override {return false;}; void removeArtifact(const ArtifactLocation & al) override {}; - bool moveArtifact(const ArtifactLocation & al1, const ArtifactLocation & al2) override {return false;}; + bool moveArtifact(const PlayerColor & player, const ArtifactLocation & al1, const ArtifactLocation & al2) override {return false;}; void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {}; void visitCastleObjects(const CGTownInstance * obj, const CGHeroInstance * hero) override {}; diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index a6cbec495..f72519213 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -290,8 +290,8 @@ void ApplyClientNetPackVisitor::visitMoveArtifact(MoveArtifact & pack) callInterfaceIfPresent(cl, player, &IGameEventsReceiver::askToAssembleArtifact, pack.dst); }; - moveArtifact(LOCPLINT->playerID); - if(cl.getOwner(pack.src.artHolder) != cl.getOwner(pack.dst.artHolder)) + moveArtifact(pack.interfaceOwner); + if(pack.interfaceOwner != cl.getOwner(pack.dst.artHolder)) moveArtifact(cl.getOwner(pack.dst.artHolder)); cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings @@ -305,7 +305,7 @@ void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack) { auto srcLoc = ArtifactLocation(pack.srcArtHolder, slotToMove.srcPos); auto dstLoc = ArtifactLocation(pack.dstArtHolder, slotToMove.dstPos); - MoveArtifact ma(&srcLoc, &dstLoc, pack.askAssemble); + MoveArtifact ma(pack.interfaceOwner, srcLoc, dstLoc, pack.askAssemble); visitMoveArtifact(ma); } }; diff --git a/client/widgets/CArtifactsOfHeroBackpack.cpp b/client/widgets/CArtifactsOfHeroBackpack.cpp index e3a54c647..88a82b2cb 100644 --- a/client/widgets/CArtifactsOfHeroBackpack.cpp +++ b/client/widgets/CArtifactsOfHeroBackpack.cpp @@ -42,11 +42,11 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack() initAOHbackpack(visibleCapacityMax, backpackCap < 0 || visibleCapacityMax < backpackCap); } -void CArtifactsOfHeroBackpack::scrollBackpack(int offset) +void CArtifactsOfHeroBackpack::onSliderMoved(int newVal) { if(backpackListBox) backpackListBox->resize(getActiveSlotRowsNum()); - backpackPos += offset; + backpackPos += newVal; auto slot = ArtifactPosition::BACKPACK_START + backpackPos; for(auto artPlace : backpack) { @@ -99,7 +99,7 @@ void CArtifactsOfHeroBackpack::initAOHbackpack(size_t slots, bool slider) }; CListBoxWithCallback::MovedPosCallback posMoved = [this](size_t pos) -> void { - scrollBackpack(static_cast(pos) * slotsColumnsMax - backpackPos); + onSliderMoved(static_cast(pos) * slotsColumnsMax - backpackPos); }; backpackListBox = std::make_shared( posMoved, onCreate, Point(0, 0), Point(0, 0), slotsRowsMax, 0, 0, 1, diff --git a/client/widgets/CArtifactsOfHeroBackpack.h b/client/widgets/CArtifactsOfHeroBackpack.h index 4a53bf790..1f6b1cf20 100644 --- a/client/widgets/CArtifactsOfHeroBackpack.h +++ b/client/widgets/CArtifactsOfHeroBackpack.h @@ -24,7 +24,7 @@ class CArtifactsOfHeroBackpack : public CArtifactsOfHeroBase public: CArtifactsOfHeroBackpack(size_t slotsColumnsMax, size_t slotsRowsMax); CArtifactsOfHeroBackpack(); - void scrollBackpack(int offset); + void onSliderMoved(int newVal); void updateBackpackSlots() override; size_t getActiveSlotRowsNum(); size_t getSlotsNum(); diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index 95151d30e..bbe53af1d 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -110,7 +110,7 @@ public: virtual bool giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * artType, ArtifactPosition pos) = 0; virtual bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional askAssemble = std::nullopt) = 0; virtual void removeArtifact(const ArtifactLocation &al) = 0; - virtual bool moveArtifact(const ArtifactLocation &al1, const ArtifactLocation &al2) = 0; + virtual bool moveArtifact(const PlayerColor & player, const ArtifactLocation & al1, const ArtifactLocation & al2) = 0; virtual void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero)=0; virtual void visitCastleObjects(const CGTownInstance * obj, const CGHeroInstance * hero)=0; diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 85e07e1bd..934dbbd26 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -1030,10 +1030,11 @@ struct DLL_LINKAGE EraseArtifact : CArtifactOperationPack struct DLL_LINKAGE MoveArtifact : CArtifactOperationPack { MoveArtifact() = default; - MoveArtifact(ArtifactLocation * src, ArtifactLocation * dst, bool askAssemble = true) - : src(*src), dst(*dst), askAssemble(askAssemble) + MoveArtifact(const PlayerColor & interfaceOwner, const ArtifactLocation & src, ArtifactLocation & dst, bool askAssemble = true) + : interfaceOwner(interfaceOwner), src(src), dst(dst), askAssemble(askAssemble) { } + PlayerColor interfaceOwner; ArtifactLocation src; ArtifactLocation dst; bool askAssemble = true; @@ -1043,6 +1044,7 @@ struct DLL_LINKAGE MoveArtifact : CArtifactOperationPack template void serialize(Handler & h) { + h & interfaceOwner; h & src; h & dst; h & askAssemble; @@ -1069,13 +1071,15 @@ struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack } }; + PlayerColor interfaceOwner; ObjectInstanceID srcArtHolder; ObjectInstanceID dstArtHolder; std::optional srcCreature; std::optional dstCreature; BulkMoveArtifacts() - : srcArtHolder(ObjectInstanceID::NONE) + : interfaceOwner(PlayerColor::NEUTRAL) + , srcArtHolder(ObjectInstanceID::NONE) , dstArtHolder(ObjectInstanceID::NONE) , swap(false) , askAssemble(false) @@ -1083,8 +1087,9 @@ struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack , dstCreature(std::nullopt) { } - BulkMoveArtifacts(const ObjectInstanceID srcArtHolder, const ObjectInstanceID dstArtHolder, bool swap) - : srcArtHolder(std::move(srcArtHolder)) + BulkMoveArtifacts(const PlayerColor & interfaceOwner, const ObjectInstanceID srcArtHolder, const ObjectInstanceID dstArtHolder, bool swap) + : interfaceOwner(interfaceOwner) + , srcArtHolder(std::move(srcArtHolder)) , dstArtHolder(std::move(dstArtHolder)) , swap(swap) , askAssemble(false) @@ -1104,6 +1109,7 @@ struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack template void serialize(Handler & h) { + h & interfaceOwner; h & artsPack0; h & artsPack1; h & srcArtHolder; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 9ee08fb8b..76f53703d 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2690,7 +2690,7 @@ bool CGameHandler::garrisonSwap(ObjectInstanceID tid) // With the amount of changes done to the function, it's more like transferArtifacts. // Function moves artifact from src to dst. If dst is not a backpack and is already occupied, old dst art goes to backpack and is replaced. -bool CGameHandler::moveArtifact(const ArtifactLocation & src, const ArtifactLocation & dst) +bool CGameHandler::moveArtifact(const PlayerColor & player, const ArtifactLocation & src, const ArtifactLocation & dst) { const auto srcArtSet = getArtSet(src); const auto dstArtSet = getArtSet(dst); @@ -2733,7 +2733,7 @@ bool CGameHandler::moveArtifact(const ArtifactLocation & src, const ArtifactLoca if(src.slot == dstSlot && src.artHolder == dst.artHolder) COMPLAIN_RET("Won't move artifact: Dest same as source!"); - BulkMoveArtifacts ma(src.artHolder, dst.artHolder, false); + BulkMoveArtifacts ma(player, src.artHolder, dst.artHolder, false); ma.srcCreature = src.creature; ma.dstCreature = dst.creature; @@ -2756,7 +2756,7 @@ bool CGameHandler::moveArtifact(const ArtifactLocation & src, const ArtifactLoca return true; } -bool CGameHandler::bulkMoveArtifacts(ObjectInstanceID srcId, ObjectInstanceID dstId, bool swap, bool equipped, bool backpack) +bool CGameHandler::bulkMoveArtifacts(const PlayerColor & player, ObjectInstanceID srcId, ObjectInstanceID dstId, bool swap, bool equipped, bool backpack) { // Make sure exchange is even possible between the two heroes. if(!isAllowedExchange(srcId, dstId)) @@ -2767,7 +2767,7 @@ bool CGameHandler::bulkMoveArtifacts(ObjectInstanceID srcId, ObjectInstanceID ds if((!psrcSet) || (!pdstSet)) COMPLAIN_RET("bulkMoveArtifacts: wrong hero's ID"); - BulkMoveArtifacts ma(srcId, dstId, swap); + BulkMoveArtifacts ma(player, srcId, dstId, swap); auto & slotsSrcDst = ma.artsPack0; auto & slotsDstSrc = ma.artsPack1; @@ -2857,12 +2857,12 @@ bool CGameHandler::bulkMoveArtifacts(ObjectInstanceID srcId, ObjectInstanceID ds return true; } -bool CGameHandler::scrollBackpackArtifacts(const ObjectInstanceID heroID, bool left) +bool CGameHandler::scrollBackpackArtifacts(const PlayerColor & player, const ObjectInstanceID heroID, bool left) { auto artSet = getArtSet(heroID); COMPLAIN_RET_FALSE_IF(artSet == nullptr, "scrollBackpackArtifacts: wrong hero's ID"); - BulkMoveArtifacts bma(heroID, heroID, false); + BulkMoveArtifacts bma(player, heroID, heroID, false); const auto backpackEnd = ArtifactPosition(ArtifactPosition::BACKPACK_START + artSet->artifactsInBackpack.size() - 1); if(backpackEnd > ArtifactPosition::BACKPACK_START) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 31b302082..5a9b0c95f 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -128,9 +128,9 @@ public: bool giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * artType, ArtifactPosition pos = ArtifactPosition::FIRST_AVAILABLE) override; bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional askAssemble) override; void removeArtifact(const ArtifactLocation &al) override; - bool moveArtifact(const ArtifactLocation & src, const ArtifactLocation & dst) override; - bool bulkMoveArtifacts(ObjectInstanceID srcId, ObjectInstanceID dstId, bool swap, bool equipped, bool backpack); - bool scrollBackpackArtifacts(const ObjectInstanceID heroID, bool left); + bool moveArtifact(const PlayerColor & player, const ArtifactLocation & src, const ArtifactLocation & dst) override; + bool bulkMoveArtifacts(const PlayerColor & player, ObjectInstanceID srcId, ObjectInstanceID dstId, bool swap, bool equipped, bool backpack); + bool scrollBackpackArtifacts(const PlayerColor & player, const ObjectInstanceID heroID, bool left); bool eraseArtifactByClient(const ArtifactLocation & al); void synchronizeArtifactHandlerLists(); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 8cadd54cd..aabe9e0bf 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -136,7 +136,7 @@ void ApplyGhNetPackVisitor::visitExchangeArtifacts(ExchangeArtifacts & pack) { if(gh.getHero(pack.src.artHolder)) gh.throwIfWrongPlayer(&pack, gh.getOwner(pack.src.artHolder)); //second hero can be ally - result = gh.moveArtifact(pack.src, pack.dst); + result = gh.moveArtifact(pack.player, pack.src, pack.dst); } void ApplyGhNetPackVisitor::visitBulkExchangeArtifacts(BulkExchangeArtifacts & pack) @@ -145,7 +145,7 @@ void ApplyGhNetPackVisitor::visitBulkExchangeArtifacts(BulkExchangeArtifacts & p gh.throwIfWrongOwner(&pack, pack.srcHero); if(pack.swap) gh.throwIfWrongOwner(&pack, pack.dstHero); - result = gh.bulkMoveArtifacts(pack.srcHero, pack.dstHero, pack.swap, pack.equipped, pack.backpack); + result = gh.bulkMoveArtifacts(pack.player, pack.srcHero, pack.dstHero, pack.swap, pack.equipped, pack.backpack); } void ApplyGhNetPackVisitor::visitManageBackpackArtifacts(ManageBackpackArtifacts & pack) @@ -153,9 +153,9 @@ void ApplyGhNetPackVisitor::visitManageBackpackArtifacts(ManageBackpackArtifacts if(gh.getPlayerRelations(pack.player, gh.getOwner(pack.artHolder)) != PlayerRelations::ENEMIES) { if(pack.cmd == ManageBackpackArtifacts::ManageCmd::SCROLL_LEFT) - result = gh.scrollBackpackArtifacts(pack.artHolder, true); + result = gh.scrollBackpackArtifacts(pack.player, pack.artHolder, true); else if(pack.cmd == ManageBackpackArtifacts::ManageCmd::SCROLL_RIGHT) - result = gh.scrollBackpackArtifacts(pack.artHolder, false); + result = gh.scrollBackpackArtifacts(pack.player, pack.artHolder, false); else { gh.throwIfWrongOwner(&pack, pack.artHolder); diff --git a/server/queries/MapQueries.cpp b/server/queries/MapQueries.cpp index 69b25d9c7..f3c62cb41 100644 --- a/server/queries/MapQueries.cpp +++ b/server/queries/MapQueries.cpp @@ -206,6 +206,9 @@ bool OpenWindowQuery::blocksPack(const CPack *pack) const if(dynamic_ptr_cast(pack) != nullptr) return false; + if(dynamic_ptr_cast(pack) != nullptr) + return false; + if(dynamic_ptr_cast(pack)) return false; diff --git a/test/mock/mock_IGameCallback.h b/test/mock/mock_IGameCallback.h index 077001c85..c42e35df9 100644 --- a/test/mock/mock_IGameCallback.h +++ b/test/mock/mock_IGameCallback.h @@ -70,7 +70,7 @@ public: bool giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * artType, ArtifactPosition pos) override {return false;} bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional askAssemble) override {return false;} void removeArtifact(const ArtifactLocation &al) override {} - bool moveArtifact(const ArtifactLocation &al1, const ArtifactLocation &al2) override {return false;} + bool moveArtifact(const PlayerColor & player, const ArtifactLocation & al1, const ArtifactLocation & al2) override {return false;} void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {} void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {} From d7607983fc3565f85bbf8c333a428c1e2b8be04a Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:21:29 +0200 Subject: [PATCH 211/250] sonarcloud warnings --- client/widgets/CArtifactsOfHeroBase.cpp | 14 +++++++------- lib/networkPacks/PacksForClient.h | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index f1e406571..fddddf73c 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -55,8 +55,8 @@ void CArtifactsOfHeroBase::setPutBackPickedArtifactCallback(PutBackPickedArtCall } void CArtifactsOfHeroBase::init( - const CArtPlace::ClickFunctor & lClickCallback, - const CArtPlace::ClickFunctor & showPopupCallback, + const CArtPlace::ClickFunctor & onClickPressedCallback, + const CArtPlace::ClickFunctor & onShowPopupCallback, const Point & position, const BpackScrollFunctor & scrollCallback) { @@ -77,14 +77,14 @@ void CArtifactsOfHeroBase::init( { artPlace.second->slot = artPlace.first; artPlace.second->setArtifact(nullptr); - artPlace.second->setClickPressedCallback(lClickCallback); - artPlace.second->setShowPopupCallback(showPopupCallback); + artPlace.second->setClickPressedCallback(onClickPressedCallback); + artPlace.second->setShowPopupCallback(onShowPopupCallback); } for(auto artPlace : backpack) { artPlace->setArtifact(nullptr); - artPlace->setClickPressedCallback(lClickCallback); - artPlace->setShowPopupCallback(showPopupCallback); + artPlace->setClickPressedCallback(onClickPressedCallback); + artPlace->setShowPopupCallback(onShowPopupCallback); } leftBackpackRoll = std::make_shared(Point(379, 364), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(), [scrollCallback](){scrollCallback(true);}, EShortcut::MOVE_LEFT); @@ -183,7 +183,7 @@ void CArtifactsOfHeroBase::updateWornSlots() void CArtifactsOfHeroBase::updateBackpackSlots() { ArtifactPosition slot = ArtifactPosition::BACKPACK_START; - for(auto & artPlace : backpack) + for(const auto & artPlace : backpack) { setSlotData(artPlace, slot); slot = slot + 1; diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 934dbbd26..25a98b460 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -1030,7 +1030,7 @@ struct DLL_LINKAGE EraseArtifact : CArtifactOperationPack struct DLL_LINKAGE MoveArtifact : CArtifactOperationPack { MoveArtifact() = default; - MoveArtifact(const PlayerColor & interfaceOwner, const ArtifactLocation & src, ArtifactLocation & dst, bool askAssemble = true) + MoveArtifact(const PlayerColor & interfaceOwner, const ArtifactLocation & src, const ArtifactLocation & dst, bool askAssemble = true) : interfaceOwner(interfaceOwner), src(src), dst(dst), askAssemble(askAssemble) { } @@ -1089,8 +1089,8 @@ struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack } BulkMoveArtifacts(const PlayerColor & interfaceOwner, const ObjectInstanceID srcArtHolder, const ObjectInstanceID dstArtHolder, bool swap) : interfaceOwner(interfaceOwner) - , srcArtHolder(std::move(srcArtHolder)) - , dstArtHolder(std::move(dstArtHolder)) + , srcArtHolder(srcArtHolder) + , dstArtHolder(dstArtHolder) , swap(swap) , askAssemble(false) , srcCreature(std::nullopt) From 6245adb9a4ba5a688322d41e7e031378878c85ac Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Fri, 8 Mar 2024 14:39:16 +0200 Subject: [PATCH 212/250] NKAI: configurable object graph --- AI/Nullkiller/AIGateway.cpp | 6 ++- .../Behaviors/CaptureObjectsBehavior.cpp | 7 ++- AI/Nullkiller/Behaviors/ClusterBehavior.cpp | 2 +- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 4 ++ AI/Nullkiller/Engine/Nullkiller.cpp | 50 +++++++++++-------- AI/Nullkiller/Engine/Settings.cpp | 8 ++- AI/Nullkiller/Engine/Settings.h | 2 + AI/Nullkiller/Pathfinding/AINodeStorage.h | 6 +-- 8 files changed, 55 insertions(+), 30 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 947fb6c7e..703461cc1 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -373,7 +373,11 @@ void AIGateway::objectRemoved(const CGObjectInstance * obj, const PlayerColor & return; nullkiller->memory->removeFromMemory(obj); - nullkiller->baseGraph->removeObject(obj); + + if(nullkiller->baseGraph && nullkiller->settings->isObjectGraphAllowed()) + { + nullkiller->baseGraph->removeObject(obj); + } if(obj->ID == Obj::HERO && obj->tempOwner == playerID) { diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index 951df981c..eac276ec5 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -178,8 +178,11 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const #endif const int3 pos = objToVisit->visitablePos(); + bool useObjectGraph = ai->nullkiller->settings->isObjectGraphAllowed() + && ai->nullkiller->getScanDepth() != ScanDepth::SMALL; + + auto paths = ai->nullkiller->pathfinder->getPathInfo(pos, useObjectGraph); - auto paths = ai->nullkiller->pathfinder->getPathInfo(pos, true); std::vector> waysToVisitObj; std::shared_ptr closestWay; @@ -210,7 +213,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const { captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects()); - if(tasks.empty()) + if(tasks.empty() || ai->nullkiller->getScanDepth() != ScanDepth::SMALL) captureObjects(ai->nullkiller->objectClusterizer->getFarObjects()); } diff --git a/AI/Nullkiller/Behaviors/ClusterBehavior.cpp b/AI/Nullkiller/Behaviors/ClusterBehavior.cpp index 4b185ed90..cf6134af3 100644 --- a/AI/Nullkiller/Behaviors/ClusterBehavior.cpp +++ b/AI/Nullkiller/Behaviors/ClusterBehavior.cpp @@ -42,7 +42,7 @@ Goals::TGoalVec ClusterBehavior::decompose() const Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr cluster) const { auto center = cluster->calculateCenter(); - auto paths = ai->nullkiller->pathfinder->getPathInfo(center->visitablePos(), true); + auto paths = ai->nullkiller->pathfinder->getPathInfo(center->visitablePos(), ai->nullkiller->settings->isObjectGraphAllowed()); auto blockerPos = cluster->blocker->visitablePos(); std::vector blockerPaths; diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 7d2ab874e..ffa9098f9 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -443,6 +443,10 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM heroToDismiss = town->garrisonHero.get(); } } + + // avoid dismissing one weak hero in order to recruit another. + if(heroToDismiss && heroToDismiss->getArmyStrength() + 500 > hero->getArmyStrength()) + continue; } else if(ai->nullkiller->heroManager->heroCapReached()) { diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index e71ed1abc..4099010c4 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -125,7 +125,7 @@ void Nullkiller::resetAiState() dangerHitMap->reset(); useHeroChain = true; - if(!baseGraph) + if(!baseGraph && ai->nullkiller->settings->isObjectGraphAllowed()) { baseGraph = std::make_unique(); baseGraph->updateGraph(this); @@ -172,20 +172,24 @@ void Nullkiller::updateAiState(int pass, bool fast) cfg.useHeroChain = useHeroChain; cfg.allowBypassObjects = true; - if(scanDepth == ScanDepth::SMALL) + if(scanDepth == ScanDepth::SMALL || settings->isObjectGraphAllowed()) { - cfg.mainTurnDistanceLimit = ai->nullkiller->settings->getMainHeroTurnDistanceLimit(); + cfg.mainTurnDistanceLimit = settings->getMainHeroTurnDistanceLimit(); } - if(scanDepth != ScanDepth::ALL_FULL) + if(scanDepth != ScanDepth::ALL_FULL || settings->isObjectGraphAllowed()) { - cfg.scoutTurnDistanceLimit = ai->nullkiller->settings->getScoutHeroTurnDistanceLimit(); + cfg.scoutTurnDistanceLimit =settings->getScoutHeroTurnDistanceLimit(); } boost::this_thread::interruption_point(); pathfinder->updatePaths(activeHeroes, cfg); - pathfinder->updateGraphs(activeHeroes); + + if(settings->isObjectGraphAllowed()) + { + pathfinder->updateGraphs(activeHeroes); + } boost::this_thread::interruption_point(); @@ -299,7 +303,8 @@ void Nullkiller::makeTurn() // TODO: better to check turn distance here instead of priority if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY) - && scanDepth == ScanDepth::MAIN_FULL) + && scanDepth == ScanDepth::MAIN_FULL + && !settings->isObjectGraphAllowed()) { useHeroChain = false; scanDepth = ScanDepth::SMALL; @@ -312,22 +317,25 @@ void Nullkiller::makeTurn() if(bestTask->priority < MIN_PRIORITY) { - auto heroes = cb->getHeroesInfo(); - auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool - { - return h->movementPointsRemaining() > 100; - }); - - if(hasMp && scanDepth != ScanDepth::ALL_FULL) + if(!settings->isObjectGraphAllowed()) { - logAi->trace( - "Goal %s has too low priority %f so increasing scan depth to full.", - taskDescription, - bestTask->priority); + auto heroes = cb->getHeroesInfo(); + auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool + { + return h->movementPointsRemaining() > 100; + }); - scanDepth = ScanDepth::ALL_FULL; - useHeroChain = false; - continue; + if(hasMp && scanDepth != ScanDepth::ALL_FULL) + { + logAi->trace( + "Goal %s has too low priority %f so increasing scan depth to full.", + taskDescription, + bestTask->priority); + + scanDepth = ScanDepth::ALL_FULL; + useHeroChain = false; + continue; + } } logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", taskDescription); diff --git a/AI/Nullkiller/Engine/Settings.cpp b/AI/Nullkiller/Engine/Settings.cpp index d004db24c..f0465e764 100644 --- a/AI/Nullkiller/Engine/Settings.cpp +++ b/AI/Nullkiller/Engine/Settings.cpp @@ -27,7 +27,8 @@ namespace NKAI mainHeroTurnDistanceLimit(10), scoutHeroTurnDistanceLimit(5), maxGoldPreasure(0.3f), - maxpass(30) + maxpass(30), + allowObjectGraph(false) { ResourcePath resource("config/ai/nkai/nkai-settings", EResType::JSON); @@ -74,5 +75,10 @@ namespace NKAI { maxGoldPreasure = node.Struct()["maxGoldPreasure"].Float(); } + + if(!node.Struct()["allowObjectGraph"].isNull()) + { + allowObjectGraph = node.Struct()["allowObjectGraph"].Bool(); + } } } \ No newline at end of file diff --git a/AI/Nullkiller/Engine/Settings.h b/AI/Nullkiller/Engine/Settings.h index 1be6aac19..c4b9510ec 100644 --- a/AI/Nullkiller/Engine/Settings.h +++ b/AI/Nullkiller/Engine/Settings.h @@ -26,6 +26,7 @@ namespace NKAI int scoutHeroTurnDistanceLimit; int maxpass; float maxGoldPreasure; + bool allowObjectGraph; public: Settings(); @@ -35,6 +36,7 @@ namespace NKAI int getMaxRoamingHeroes() const { return maxRoamingHeroes; } int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; } int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; } + bool isObjectGraphAllowed() const { return allowObjectGraph; } private: void loadFromMod(const std::string & modName, const ResourcePath & resource); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 556402d76..818ded9c8 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -12,7 +12,7 @@ #define NKAI_PATHFINDER_TRACE_LEVEL 0 #define NKAI_GRAPH_TRACE_LEVEL 0 -#define NKAI_TRACE_LEVEL 0 +#define NKAI_TRACE_LEVEL 1 #include "../../../lib/pathfinder/CGPathNode.h" #include "../../../lib/pathfinder/INodeStorage.h" @@ -27,11 +27,9 @@ namespace NKAI { namespace AIPathfinding { - - const int BUCKET_COUNT = 5; + const int BUCKET_COUNT = 5; const int BUCKET_SIZE = 3; const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE; - const int THREAD_COUNT = 8; const int CHAIN_MAX_DEPTH = 4; } From 2c1b142d68df0ab5164466039e99e104f0c07b1d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 4 Mar 2024 14:11:09 +0200 Subject: [PATCH 213/250] Added support for 'const' field to json validator --- lib/json/JsonValidator.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/json/JsonValidator.cpp b/lib/json/JsonValidator.cpp index 14dca501d..0cb33f937 100644 --- a/lib/json/JsonValidator.cpp +++ b/lib/json/JsonValidator.cpp @@ -105,6 +105,14 @@ static std::string enumCheck(JsonValidator & validator, const JsonNode & baseSch return validator.makeErrorMessage("Key must have one of predefined values"); } +static std::string constCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) +{ + if (data == schema) + return ""; + + return validator.makeErrorMessage("Key must have have constant value"); +} + static std::string typeCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data) { static const std::unordered_map stringToType = @@ -496,6 +504,7 @@ JsonValidator::TValidatorMap createCommonFields() ret["anyOf"] = anyOfCheck; ret["oneOf"] = oneOfCheck; ret["enum"] = enumCheck; + ret["const"] = constCheck; ret["type"] = typeCheck; ret["not"] = notCheck; ret["$ref"] = refCheck; From 5b1549200a8a7e31b67d497baa0887fcd65cdc0a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 4 Mar 2024 14:11:45 +0200 Subject: [PATCH 214/250] Renamed openGameRoom to more clear activateGameRoom --- client/globalLobby/GlobalLobbyClient.cpp | 4 ++-- lobby/LobbyServer.cpp | 6 +++--- lobby/LobbyServer.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index b5e398b9a..d5163afd3 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -279,7 +279,7 @@ void GlobalLobbyClient::sendMessage(const JsonNode & data) void GlobalLobbyClient::sendOpenPublicRoom() { JsonNode toSend; - toSend["type"].String() = "openGameRoom"; + toSend["type"].String() = "activateGameRoom"; toSend["hostAccountID"] = settings["lobby"]["accountID"]; toSend["roomType"].String() = "public"; sendMessage(toSend); @@ -288,7 +288,7 @@ void GlobalLobbyClient::sendOpenPublicRoom() void GlobalLobbyClient::sendOpenPrivateRoom() { JsonNode toSend; - toSend["type"].String() = "openGameRoom"; + toSend["type"].String() = "activateGameRoom"; toSend["hostAccountID"] = settings["lobby"]["accountID"]; toSend["roomType"].String() = "private"; sendMessage(toSend); diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 45af54a1a..816d12bdb 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -258,8 +258,8 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons if(messageType == "sendChatMessage") return receiveSendChatMessage(connection, json); - if(messageType == "openGameRoom") - return receiveOpenGameRoom(connection, json); + if(messageType == "activateGameRoom") + return receiveActivateGameRoom(connection, json); if(messageType == "joinGameRoom") return receiveJoinGameRoom(connection, json); @@ -464,7 +464,7 @@ void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connectio //connection->close(); } -void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json) +void LobbyServer::receiveActivateGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json) { std::string hostAccountID = json["hostAccountID"].String(); std::string accountID = activeAccounts[connection]; diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index e61b0111c..e2437495c 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -77,7 +77,7 @@ class LobbyServer final : public INetworkServerListener 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 receiveActivateGameRoom(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); From 6120fd2faa4c5081af8c1ae2b873d2f03f6d9826 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 4 Mar 2024 14:12:15 +0200 Subject: [PATCH 215/250] Lobby server will now validate incoming messages --- lobby/LobbyServer.cpp | 45 +++++++++++++++++++++++++++++++++++++++---- lobby/LobbyServer.h | 4 ++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 816d12bdb..faefa5b79 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -12,7 +12,9 @@ #include "LobbyDatabase.h" +#include "../lib/json/JsonFormatException.h" #include "../lib/json/JsonNode.h" +#include "../lib/json/JsonUtils.h" #include #include @@ -230,6 +232,44 @@ void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection, const broadcastActiveGameRooms(); } +JsonNode LobbyServer::parseAndValidateMessage(const std::vector & message) const +{ + JsonParsingSettings parserSettings; + parserSettings.mode = JsonParsingSettings::JsonFormatMode::JSON; + parserSettings.maxDepth = 2; + parserSettings.strict = true; + + JsonNode json; + try + { + JsonNode jsonTemp(message.data(), message.size()); + json = std::move(jsonTemp); + } + catch (const JsonFormatException & e) + { + logGlobal->info(std::string("Json parsing error encountered: ") + e.what()); + return JsonNode(); + } + + std::string messageType = json["type"].String(); + + if (messageType.empty()) + { + logGlobal->info("Json parsing error encountered: Message type not set!"); + return JsonNode(); + } + + std::string schemaName = "vcmi:lobbyProtocol/" + messageType; + + if (!JsonUtils::validate(json, schemaName, "network")) + { + logGlobal->info("Json validation error encountered!"); + return JsonNode(); + } + + return json; +} + void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, const std::vector & message) { // proxy connection - no processing, only redirect @@ -242,10 +282,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons logGlobal->info("Received unexpected message for inactive proxy!"); } - JsonNode json(message.data(), message.size()); - - // TODO: check for json parsing errors - // TODO: validate json based on received message type + JsonNode json = parseAndValidateMessage(message); std::string messageType = json["type"].String(); diff --git a/lobby/LobbyServer.h b/lobby/LobbyServer.h index e2437495c..f263ab03e 100644 --- a/lobby/LobbyServer.h +++ b/lobby/LobbyServer.h @@ -61,6 +61,10 @@ class LobbyServer final : public INetworkServerListener JsonNode prepareActiveGameRooms(); + /// Attempts to load json from incoming byte stream and validate it + /// Returns parsed json on success or empty json node on failure + JsonNode parseAndValidateMessage(const std::vector & message) const; + 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 sendOperationFailed(const NetworkConnectionPtr & target, const std::string & reason); From ea04a8481257336d0fae8ef9461d744b477e5465 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 4 Mar 2024 14:12:42 +0200 Subject: [PATCH 216/250] Added initial versions of schemas for lobby protocol --- .../schemas/lobbyProtocol/accountCreated.json | 26 ++++++++ .../lobbyProtocol/accountJoinsRoom.json | 21 +++++++ .../lobbyProtocol/activateGameRoom.json | 27 +++++++++ .../schemas/lobbyProtocol/activeAccounts.json | 45 ++++++++++++++ .../lobbyProtocol/activeGameRooms.json | 59 +++++++++++++++++++ config/schemas/lobbyProtocol/chatHistory.json | 49 +++++++++++++++ config/schemas/lobbyProtocol/chatMessage.json | 38 ++++++++++++ config/schemas/lobbyProtocol/clientLogin.json | 37 ++++++++++++ .../lobbyProtocol/clientProxyLogin.json | 31 ++++++++++ .../schemas/lobbyProtocol/clientRegister.json | 31 ++++++++++ .../schemas/lobbyProtocol/declineInvite.json | 21 +++++++ .../schemas/lobbyProtocol/inviteReceived.json | 26 ++++++++ .../schemas/lobbyProtocol/joinGameRoom.json | 21 +++++++ .../lobbyProtocol/joinRoomSuccess.json | 26 ++++++++ .../schemas/lobbyProtocol/leaveGameRoom.json | 21 +++++++ .../schemas/lobbyProtocol/loginSuccess.json | 26 ++++++++ .../lobbyProtocol/operationFailed.json | 21 +++++++ .../lobbyProtocol/sendChatMessage.json | 21 +++++++ config/schemas/lobbyProtocol/sendInvite.json | 21 +++++++ config/schemas/lobbyProtocol/serverLogin.json | 37 ++++++++++++ .../lobbyProtocol/serverProxyLogin.json | 32 ++++++++++ 21 files changed, 637 insertions(+) create mode 100644 config/schemas/lobbyProtocol/accountCreated.json create mode 100644 config/schemas/lobbyProtocol/accountJoinsRoom.json create mode 100644 config/schemas/lobbyProtocol/activateGameRoom.json create mode 100644 config/schemas/lobbyProtocol/activeAccounts.json create mode 100644 config/schemas/lobbyProtocol/activeGameRooms.json create mode 100644 config/schemas/lobbyProtocol/chatHistory.json create mode 100644 config/schemas/lobbyProtocol/chatMessage.json create mode 100644 config/schemas/lobbyProtocol/clientLogin.json create mode 100644 config/schemas/lobbyProtocol/clientProxyLogin.json create mode 100644 config/schemas/lobbyProtocol/clientRegister.json create mode 100644 config/schemas/lobbyProtocol/declineInvite.json create mode 100644 config/schemas/lobbyProtocol/inviteReceived.json create mode 100644 config/schemas/lobbyProtocol/joinGameRoom.json create mode 100644 config/schemas/lobbyProtocol/joinRoomSuccess.json create mode 100644 config/schemas/lobbyProtocol/leaveGameRoom.json create mode 100644 config/schemas/lobbyProtocol/loginSuccess.json create mode 100644 config/schemas/lobbyProtocol/operationFailed.json create mode 100644 config/schemas/lobbyProtocol/sendChatMessage.json create mode 100644 config/schemas/lobbyProtocol/sendInvite.json create mode 100644 config/schemas/lobbyProtocol/serverLogin.json create mode 100644 config/schemas/lobbyProtocol/serverProxyLogin.json diff --git a/config/schemas/lobbyProtocol/accountCreated.json b/config/schemas/lobbyProtocol/accountCreated.json new file mode 100644 index 000000000..f6d012916 --- /dev/null +++ b/config/schemas/lobbyProtocol/accountCreated.json @@ -0,0 +1,26 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: accountCreated", + "description" : "Sent by server when player successfully creates a new account. Note that it does not automatically logs in created account", + "required" : [ "type", "accountID", "accountCookie" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "accountCreated" + }, + "accountID" : + { + "type" : "string", + "description" : "Unique ID of account that client must remember for future login attempts" + }, + "accountCookie" : + { + "type" : "string", + "description" : "Private access cookie of account that client must remember for future login attempts" + } + } +} diff --git a/config/schemas/lobbyProtocol/accountJoinsRoom.json b/config/schemas/lobbyProtocol/accountJoinsRoom.json new file mode 100644 index 000000000..4a1b8873c --- /dev/null +++ b/config/schemas/lobbyProtocol/accountJoinsRoom.json @@ -0,0 +1,21 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: accountJoinsRoom", + "description" : "Sent by server to match server when new account joins the room", + "required" : [ "type", "accountID" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "accountJoinsRoom" + }, + "accountID" : + { + "type" : "string", + "description" : "ID of account that have joined the room" + } + } +} diff --git a/config/schemas/lobbyProtocol/activateGameRoom.json b/config/schemas/lobbyProtocol/activateGameRoom.json new file mode 100644 index 000000000..08b1f995d --- /dev/null +++ b/config/schemas/lobbyProtocol/activateGameRoom.json @@ -0,0 +1,27 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: activateGameRoom", + "description" : "Sent by client when player wants to activate a game room", + "required" : [ "accountID", "accountCookie", "language", "version" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "activateGameRoom" + }, + "hostAccountID" : + { + "type" : "string", + "description" : "Account ID that hosts match server that player wants to activate" + }, + "roomType" : + { + "type" : "string", + "enum" : [ "public", "private" ], + "description" : "Room type to use for activation" + } + } +} diff --git a/config/schemas/lobbyProtocol/activeAccounts.json b/config/schemas/lobbyProtocol/activeAccounts.json new file mode 100644 index 000000000..20b93312f --- /dev/null +++ b/config/schemas/lobbyProtocol/activeAccounts.json @@ -0,0 +1,45 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: activeAccounts", + "description" : "Sent by server to initialized or update list of active accounts", + "required" : [ "type", "accounts" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "activeAccounts" + }, + "accounts" : + { + "type" : "array", + "description" : "List of accounts that are currently online" + "items" : + { + "type" : "object", + "additionalProperties" : false, + "required" : [ "accountID", "displayName", "status" ], + + "properties" : { + "accountID" : + { + "type" : "string", + "description" : "Unique ID of an account" + }, + "displayName" : + { + "type" : "string", + "description" : "Display name of an account" + }, + "status" : + { + "type" : "string", + "description" : "Current status of an account, such as in lobby or in match" + } + } + } + } + } +} diff --git a/config/schemas/lobbyProtocol/activeGameRooms.json b/config/schemas/lobbyProtocol/activeGameRooms.json new file mode 100644 index 000000000..c7b7c9ba3 --- /dev/null +++ b/config/schemas/lobbyProtocol/activeGameRooms.json @@ -0,0 +1,59 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: activeGameRooms", + "description" : "Sent by server to initialized or update list of game rooms", + "required" : [ "type", "gameRooms" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "activeGameRooms" + }, + "gameRooms" : + { + "type" : "array", + "description" : "List of currently available game rooms" + "items" : + { + "type" : "object", + "additionalProperties" : false, + "required" : [ "gameRoomID", "hostAccountID", "hostAccountDisplayName", "description", "playersCount", "playersLimit" ], + "properties" : { + "gameRoomID" : + { + "type" : "string", + "description" : "Unique ID of game room" + }, + "hostAccountID" : + { + "type" : "string", + "description" : "ID of account that created and hosts this game room" + }, + "hostAccountDisplayName" : + { + "type" : "string", + "description" : "Display name of account that created and hosts this game room" + }, + "description" : + { + "type" : "string", + "description" : "Auto-generated description of this room" + }, + "playersCount" : + { + "type" : "number", + "description" : "Current number of players in this room, including host" + }, + "playersLimit" : + { + "type" : "number", + "description" : "Maximum number of players that can join this room, including host" + } + } + } + } + } +} diff --git a/config/schemas/lobbyProtocol/chatHistory.json b/config/schemas/lobbyProtocol/chatHistory.json new file mode 100644 index 000000000..78b74431f --- /dev/null +++ b/config/schemas/lobbyProtocol/chatHistory.json @@ -0,0 +1,49 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: chatHistory", + "description" : "Sent by server immediately after login to fill initial chat history", + "required" : [ "type", "messages" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "chatHistory" + }, + "messages" : + { + "type" : "array", + "description" : "List of recent chat messages" + "items" : + { + "type" : "object", + "additionalProperties" : false, + "required" : [ "messageText", "accountID", "displayName", "ageSeconds" ], + "properties" : { + "messageText" : + { + "type" : "string", + "description" : "Text of sent message" + }, + "accountID" : + { + "type" : "string", + "description" : "ID of account that have sent this message" + }, + "displayName" : + { + "type" : "string", + "description" : "Display name of account that have sent this message" + }, + "ageSeconds" : + { + "type" : "number", + "description" : "Age of this message in seconds. For example, 10 means that this message was sent 10 seconds ago" + } + } + } + } + } +} diff --git a/config/schemas/lobbyProtocol/chatMessage.json b/config/schemas/lobbyProtocol/chatMessage.json new file mode 100644 index 000000000..825edf43c --- /dev/null +++ b/config/schemas/lobbyProtocol/chatMessage.json @@ -0,0 +1,38 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: chatMessage", + "description" : "Sent by server to all players in lobby whenever new message is sent to game chat", + "required" : [ "type", "messageText", "accountID", "displayName", "roomMode", "roomName" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "chatMessage" + }, + "messageText" : + { + "type" : "string", + "description" : "Text of sent message" + }, + "accountID" : + { + "type" : "string", + "description" : "ID of account that have sent this message" + }, + "roomMode" : + { + "type" : "string", + "const" : "general", + "description" : "Type of room to which this message has been set. Right now can only be 'general'" + }, + "roomName" : + { + "type" : "string", + "const" : "english", + "description" : "Name of room to which this message has been set. Right now only 'english' is used" + } + } +} diff --git a/config/schemas/lobbyProtocol/clientLogin.json b/config/schemas/lobbyProtocol/clientLogin.json new file mode 100644 index 000000000..1bd1c3294 --- /dev/null +++ b/config/schemas/lobbyProtocol/clientLogin.json @@ -0,0 +1,37 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: clientLogin", + "description" : "Sent by client when player requests lobby login", + + "required" : [ "type", "accountID", "accountCookie", "language", "version" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "clientLogin" + }, + "accountID" : + { + "type" : "string", + "description" : "ID of existing account to login with" + }, + "accountCookie" : + { + "type" : "string", + "description" : "Secret UUID for identification" + }, + "language" : + { + "type" : "string", + "description" : "Language of client, see lib/Languages.h for full list" + }, + "version" : + { + "type" : "string", + "description" : "Version of client, e.g. 1.5.0" + } + } +} diff --git a/config/schemas/lobbyProtocol/clientProxyLogin.json b/config/schemas/lobbyProtocol/clientProxyLogin.json new file mode 100644 index 000000000..5b4d5d05b --- /dev/null +++ b/config/schemas/lobbyProtocol/clientProxyLogin.json @@ -0,0 +1,31 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: clientProxyLogin", + "description" : "Sent by client when player attempt to connect to game room in proxy mode", + "required" : [ "type", "accountID", "accountCookie", "gameRoomID" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "clientLogin" + }, + "accountID" : + { + "type" : "string", + "description" : "ID of existing account to login with" + }, + "accountCookie" : + { + "type" : "string", + "description" : "Secret UUID for identification" + }, + "gameRoomID" : + { + "type" : "string", + "description" : "ID of game room to connect to" + } + } +} diff --git a/config/schemas/lobbyProtocol/clientRegister.json b/config/schemas/lobbyProtocol/clientRegister.json new file mode 100644 index 000000000..c0dc3a716 --- /dev/null +++ b/config/schemas/lobbyProtocol/clientRegister.json @@ -0,0 +1,31 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: clientRegister", + "description" : "Sent by client when player attempts to create a new account", + "required" : [ "type", "displayName", "language", "version" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "clientLogin" + }, + "displayName" : + { + "type" : "string", + "description" : "Desired name of a new account" + }, + "language" : + { + "type" : "string", + "description" : "Language of client, see lib/Languages.h for full list" + }, + "version" : + { + "type" : "string", + "description" : "Version of client, e.g. 1.5.0" + } + } +} diff --git a/config/schemas/lobbyProtocol/declineInvite.json b/config/schemas/lobbyProtocol/declineInvite.json new file mode 100644 index 000000000..db8f80909 --- /dev/null +++ b/config/schemas/lobbyProtocol/declineInvite.json @@ -0,0 +1,21 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: declineInvite", + "description" : "Sent by client when player declines invite to join a room", + "required" : [ "type", "gameRoomID" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "declineInvite" + }, + "gameRoomID" : + { + "type" : "string", + "description" : "ID of game room to decline invite" + } + } +} diff --git a/config/schemas/lobbyProtocol/inviteReceived.json b/config/schemas/lobbyProtocol/inviteReceived.json new file mode 100644 index 000000000..3668bf154 --- /dev/null +++ b/config/schemas/lobbyProtocol/inviteReceived.json @@ -0,0 +1,26 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: inviteReceived", + "description" : "Sent by server when player receives an invite from another player to join the game room", + "required" : [ "type", "accountID", "gameRoomID" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "inviteReceived" + }, + "accountID" : + { + "type" : "string", + "description" : "ID of account that sent an invite" + }, + "gameRoomID" : + { + "type" : "string", + "description" : "ID of game room that this player is now allowed to join" + } + } +} diff --git a/config/schemas/lobbyProtocol/joinGameRoom.json b/config/schemas/lobbyProtocol/joinGameRoom.json new file mode 100644 index 000000000..e1beb3939 --- /dev/null +++ b/config/schemas/lobbyProtocol/joinGameRoom.json @@ -0,0 +1,21 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: joinGameRoom", + "description" : "Sent by client when player attempts to join a game room", + "required" : [ "type", "gameRoomID" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "joinGameRoom" + }, + "gameRoomID" : + { + "type" : "string", + "description" : "ID of game room to join" + } + } +} diff --git a/config/schemas/lobbyProtocol/joinRoomSuccess.json b/config/schemas/lobbyProtocol/joinRoomSuccess.json new file mode 100644 index 000000000..a1b6dd8f5 --- /dev/null +++ b/config/schemas/lobbyProtocol/joinRoomSuccess.json @@ -0,0 +1,26 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: joinRoomSuccess", + "description" : "Sent by server when player successfully joins the game room", + "required" : [ "type", "gameRoomID", "proxyMode" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "joinRoomSuccess" + }, + "gameRoomID" : + { + "type" : "string", + "description" : "ID of game room that this player now participates in" + }, + "proxyMode" : + { + "type" : "boolean", + "description" : "If true, account should use proxy mode and connect to server via game lobby" + } + } +} diff --git a/config/schemas/lobbyProtocol/leaveGameRoom.json b/config/schemas/lobbyProtocol/leaveGameRoom.json new file mode 100644 index 000000000..06d31b08c --- /dev/null +++ b/config/schemas/lobbyProtocol/leaveGameRoom.json @@ -0,0 +1,21 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: leaveGameRoom", + "description" : "Sent by server when player disconnects from the game room", + "required" : [ "type", "accountID" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "leaveGameRoom" + }, + "accountID" : + { + "type" : "string", + "description" : "ID of account that have left the room" + } + } +} diff --git a/config/schemas/lobbyProtocol/loginSuccess.json b/config/schemas/lobbyProtocol/loginSuccess.json new file mode 100644 index 000000000..d31b4d361 --- /dev/null +++ b/config/schemas/lobbyProtocol/loginSuccess.json @@ -0,0 +1,26 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: loginSuccess", + "description" : "Sent by server once player sucesfully logs into the lobby", + "required" : [ "type", "accountCookie", "displayName" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "loginSuccess" + }, + "accountCookie" : + { + "type" : "string", + "description" : "Security cookie that should be stored by client and used for future operations" + }, + "displayName" : + { + "type" : "string", + "description" : "Account display name - how client should display this account" + } + } +} diff --git a/config/schemas/lobbyProtocol/operationFailed.json b/config/schemas/lobbyProtocol/operationFailed.json new file mode 100644 index 000000000..9b1ab8d9d --- /dev/null +++ b/config/schemas/lobbyProtocol/operationFailed.json @@ -0,0 +1,21 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: operationFailed", + "description" : "Sent by server whenever operation requested by client fails", + "required" : [ "type", "reason" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "operationFailed" + }, + "reason" : + { + "type" : "string", + "description" : "Human-readable description of reason behind operation failure, in English" + } + } +} diff --git a/config/schemas/lobbyProtocol/sendChatMessage.json b/config/schemas/lobbyProtocol/sendChatMessage.json new file mode 100644 index 000000000..6de400ae4 --- /dev/null +++ b/config/schemas/lobbyProtocol/sendChatMessage.json @@ -0,0 +1,21 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: sendChatMessage", + "description" : "Sent by client when player requests lobby login", + "required" : [ "accountID", "accountCookie", "language", "version" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "sendChatMessage" + }, + "messageText" : + { + "type" : "string", + "description" : "Text of sent message" + } + } +} diff --git a/config/schemas/lobbyProtocol/sendInvite.json b/config/schemas/lobbyProtocol/sendInvite.json new file mode 100644 index 000000000..6dfda4e72 --- /dev/null +++ b/config/schemas/lobbyProtocol/sendInvite.json @@ -0,0 +1,21 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: sendInvite", + "description" : "Sent by client when player invites another player into his current game room", + "required" : [ "type", "accountID" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "sendInvite" + }, + "accountID" : + { + "type" : "string", + "description" : "ID of account to invite into the room" + } + } +} diff --git a/config/schemas/lobbyProtocol/serverLogin.json b/config/schemas/lobbyProtocol/serverLogin.json new file mode 100644 index 000000000..5abd0adb7 --- /dev/null +++ b/config/schemas/lobbyProtocol/serverLogin.json @@ -0,0 +1,37 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: serverLogin", + "description" : "Sent by match server when starting up in lobby mode", + + "required" : [ "type", "gameRoomID", "accountID", "accountCookie", "version" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "serverLogin" + }, + "gameRoomID" : + { + "type" : "string", + "description" : "UUID generated by server to represent this game room" + }, + "accountID" : + { + "type" : "string", + "description" : "Account ID of game host" + }, + "accountCookie" : + { + "type" : "string", + "description" : "Security cookie of host account" + }, + "version" : + { + "type" : "string", + "description" : "Version of match server, e.g. 1.5.0" + } + } +} diff --git a/config/schemas/lobbyProtocol/serverProxyLogin.json b/config/schemas/lobbyProtocol/serverProxyLogin.json new file mode 100644 index 000000000..f93494032 --- /dev/null +++ b/config/schemas/lobbyProtocol/serverProxyLogin.json @@ -0,0 +1,32 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-06/schema", + "title" : "Lobby protocol: serverProxyLogin", + "description" : "Sent by match server to establish proxy connection to be used by player", + + "required" : [ "type", "gameRoomID", "guestAccountID", "accountCookie" ], + "additionalProperties" : false, + + "properties" : { + "type" : + { + "type" : "string", + "const" : "serverProxyLogin" + }, + "gameRoomID" : + { + "type" : "string", + "description" : "ID of server game room to login with" + }, + "guestAccountID" : + { + "type" : "string", + "description" : "Account ID of player that should use this connection" + }, + "accountCookie" : + { + "type" : "string", + "description" : "Secret UUID for identification of account that hosts match server" + } + } +} From b628a2460c72c7ff44be20aab1d1d76e5b2680ad Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 4 Mar 2024 16:32:14 +0200 Subject: [PATCH 217/250] Initial version of networking docs --- docs/developers/Networking.md | 104 +++++++++++++++++++++++++++++++ docs/developers/Serialization.md | 7 +-- 2 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 docs/developers/Networking.md diff --git a/docs/developers/Networking.md b/docs/developers/Networking.md new file mode 100644 index 000000000..155ad2ad8 --- /dev/null +++ b/docs/developers/Networking.md @@ -0,0 +1,104 @@ +< [Documentation](../Readme.md) / Networking + +# The big picture + +For implementation details see files located at `lib/network` directory. + +VCMI uses connection using TCP to communicate with server, even in single-player games. However, even though TCP is stream-based protocol, VCMI uses atomic messages for communication. Each message is a serialized stream of bytes, preceded by 4-byte message size: +``` +int32_t messageSize; +byte messagePayload[messageSize]; +``` + +Networking can be used by: +- game client (vcmiclient / VCMI_Client.exe). Actual application that player interacts with directly using UI. +- match server (vcmiserver / VCMI_Server.exe / part of game client). This app controls game logic and coordinates multiplayer games. +- lobby server (vcmilobby). This app provides access to global lobby through which players can play game over Internet. + +Following connections can be established during game lifetime: + +- game client -> match server: This is main connection for use during gameplay, created once player requests to move from main menu to pregame / match lobby (e.g. after pressing New Game / Load Game) +- game client -> lobby server: This connection is used to access global lobby, for multiplayer over Internet. Created when player logs into a lobby (Multiplayer -> Connect to global service) +- match server -> lobby server: This connection is established when player creates new multiplayer room via lobby. It is used by lobby server to send commands to match server + +# Gameplay communication + +For gameplay, VCMI serializes data into a binary stream. See [Serialization](Serialization.md) for more information. + +# Global lobby communication + +For implementation details see: +- game client: `client/globalLobby/GlobalLobbyClient.h +- match server: `server/GlobalLobbyProcessor.h +- lobby server: `client/globalLobby/GlobalLobbyClient.h + +In case of global lobby, message payload uses plaintext json format - utf-8 encoded string: +``` +int32_t messageSize; +char jsonString[messageSize]; +``` + +Every message must be a struct (json object) that contains "type" field. Unlike rest of VCMI codebase, this message is validated as strict json, without any extensions, such as comments. + +## Communication flow + +Notes: +- invalid message, such as corrupted json format or failure to validate message will result in no reply from server +- in addition to specified messages, match server will send `operationFailed` message on failure to apply player request + +### New Account Creation + +- client -> lobby: `clientRegister` +- lobby -> client: `accountCreated` + +### Login +- client -> lobby: `clientLogin` +- lobby -> client: `loginSuccess` +- lobby -> client: `chatHistory` +- lobby -> client: `activeAccounts` +- lobby -> client: `activeGameRooms` + +### Chat Message +- client -> lobby: `sendChatMessage` +- lobby -> every client: `chatMessage` + +### New Game Room +- client starts match server instance +- match -> lobby: `serverLogin` +- lobby -> match: `loginSuccess` +- match accepts connection from client +- client -> lobby: `activateGameRoom` +- lobby -> client: `joinRoomSuccess` +- lobby -> every client: `activeAccounts` +- lobby -> every client: `activeGameRooms` + +### Joining a game room +See [#Proxy mode](proxy-mode) + +### Leaving a game room +- client closes connection to match server +- match -> lobby: `leaveGameRoom` + +### Sending an invite: +- client -> lobby: `sendInvite` +- lobby -> target client: `inviteReceived` + +Note: there is no dedicated procedure to accept an invite. Instead, invited player will use same flow as when joining public game room + +### Logout +- client closes connection +- lobby -> every client: `activeAccounts` + +## Proxy mode + +In order to connect players located behind NAT, VCMI lobby can operate in "proxy" mode. In this mode, connection will be act as proxy and will transmit gameplay data from client to a match server, without any data processing on lobby server. + +Currently, process to establish connection using proxy mode is: +- Player attempt to join open game room using `joinGameRoom` message +- Lobby server validates requests and on success - notifies match server about new player in lobby using control connection +- Match server receives request, establishes new connection to game lobby, sends `serverProxyLogin` message to lobby server and immediately transfers this connection to VCMIServer class to use as connection for gameplay communication +- Lobby server accepts new connection and then sends reply to client using `joinRoomSuccess` message. +- Game client receives message and establishes own side of proxy connection - connects to lobby, sends `clientProxyLogin` message and transfers to ServerHandler class to use as connection for gameplay communication +- Lobby server accepts new connection and moves it into a proxy mode - all packages that will be received by one side of this connection will be re-sent to another side without any processing. + +Once the game is over (or if one side disconnects) lobby server will close another side of the connection and erase proxy connection \ No newline at end of file diff --git a/docs/developers/Serialization.md b/docs/developers/Serialization.md index 726530313..6bfbd47fd 100644 --- a/docs/developers/Serialization.md +++ b/docs/developers/Serialization.md @@ -140,12 +140,7 @@ CLoadFile/CSaveFile classes allow to read data to file and store data to file. T ### Networking -CConnection class provides support for sending data structures over TCP/IP protocol. It provides 3 constructors: -1. connect to given hostname at given port (host must be accepting connection) -2. accept connection (takes boost.asio acceptor and io_service) -3. adapt boost.asio socket with already established connection - -All three constructors take as the last parameter the name that will be used to identify the peer. +See [Networking](Networking.md) ## Additional features From 0946f5e6906d8fad0b68ad9a86a86f5719887d09 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 4 Mar 2024 16:32:29 +0200 Subject: [PATCH 218/250] Fix file name in header --- lib/networkPacks/EntityChanges.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/networkPacks/EntityChanges.h b/lib/networkPacks/EntityChanges.h index f502c309d..1ee808364 100644 --- a/lib/networkPacks/EntityChanges.h +++ b/lib/networkPacks/EntityChanges.h @@ -1,5 +1,5 @@ /* - * EInfoWindowMode.h, part of VCMI engine + * EntityChanges.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * From c9604f3dc13e94502455cc9b02fdd537dedf5d70 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 4 Mar 2024 16:39:49 +0200 Subject: [PATCH 219/250] Fix json formatting --- config/schemas/lobbyProtocol/activeAccounts.json | 2 +- config/schemas/lobbyProtocol/activeGameRooms.json | 2 +- config/schemas/lobbyProtocol/chatHistory.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/schemas/lobbyProtocol/activeAccounts.json b/config/schemas/lobbyProtocol/activeAccounts.json index 20b93312f..1931c7805 100644 --- a/config/schemas/lobbyProtocol/activeAccounts.json +++ b/config/schemas/lobbyProtocol/activeAccounts.json @@ -15,7 +15,7 @@ "accounts" : { "type" : "array", - "description" : "List of accounts that are currently online" + "description" : "List of accounts that are currently online", "items" : { "type" : "object", diff --git a/config/schemas/lobbyProtocol/activeGameRooms.json b/config/schemas/lobbyProtocol/activeGameRooms.json index c7b7c9ba3..63618fd40 100644 --- a/config/schemas/lobbyProtocol/activeGameRooms.json +++ b/config/schemas/lobbyProtocol/activeGameRooms.json @@ -15,7 +15,7 @@ "gameRooms" : { "type" : "array", - "description" : "List of currently available game rooms" + "description" : "List of currently available game rooms", "items" : { "type" : "object", diff --git a/config/schemas/lobbyProtocol/chatHistory.json b/config/schemas/lobbyProtocol/chatHistory.json index 78b74431f..e7800f490 100644 --- a/config/schemas/lobbyProtocol/chatHistory.json +++ b/config/schemas/lobbyProtocol/chatHistory.json @@ -15,7 +15,7 @@ "messages" : { "type" : "array", - "description" : "List of recent chat messages" + "description" : "List of recent chat messages", "items" : { "type" : "object", From 456dfd9e3d9591a0c6988294faaddda2d71f92cf Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 4 Mar 2024 22:13:06 +0200 Subject: [PATCH 220/250] Added debug validation of lobby protocol on every send/receive --- client/globalLobby/GlobalLobbyClient.cpp | 3 +++ lobby/LobbyServer.cpp | 2 ++ server/GlobalLobbyProcessor.cpp | 7 +++++++ 3 files changed, 12 insertions(+) diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index d5163afd3..7a80a690b 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -22,6 +22,7 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/MetaString.h" +#include "../../lib/json/JsonUtils.h" #include "../../lib/TextOperations.h" GlobalLobbyClient::GlobalLobbyClient() = default; @@ -273,6 +274,7 @@ void GlobalLobbyClient::onDisconnected(const std::shared_ptr void GlobalLobbyClient::sendMessage(const JsonNode & data) { + assert(JsonUtils::validate(data, "vcmi:lobbyProtocol/" + data["type"].String(), "network")); networkConnection->sendPacket(data.toBytes()); } @@ -362,5 +364,6 @@ void GlobalLobbyClient::sendProxyConnectionLogin(const NetworkConnectionPtr & ne toSend["accountCookie"] = settings["lobby"]["accountCookie"]; toSend["gameRoomID"] = settings["lobby"]["roomID"]; + assert(JsonUtils::validate(toSend, "vcmi:lobbyProtocol/" + toSend["type"].String(), "network")); netConnection->sendPacket(toSend.toBytes()); } diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index faefa5b79..88683e4bc 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -60,6 +60,7 @@ NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) c void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json) { + assert(JsonUtils::validate(json, "vcmi:lobbyProtocol/" + json["type"].String(), "network")); target->sendPacket(json.toBytes()); } @@ -264,6 +265,7 @@ JsonNode LobbyServer::parseAndValidateMessage(const std::vector & mes if (!JsonUtils::validate(json, schemaName, "network")) { logGlobal->info("Json validation error encountered!"); + assert(0); return JsonNode(); } diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index f06d7c19c..ca6fb6828 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -12,6 +12,7 @@ #include "CVCMIServer.h" #include "../lib/CConfigHandler.h" +#include "../lib/json/JsonUtils.h" GlobalLobbyProcessor::GlobalLobbyProcessor(CVCMIServer & owner) : owner(owner) @@ -45,6 +46,8 @@ void GlobalLobbyProcessor::onDisconnected(const std::shared_ptrsendPacket(message.toBytes()); break; } @@ -122,6 +125,8 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptrsendPacket(toSend.toBytes()); } else @@ -137,6 +142,8 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptrsendPacket(toSend.toBytes()); proxyConnections[guestAccountID] = connection; From efe10b05e98445577e134c5a6a3cc24e83a9ac11 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 8 Mar 2024 14:22:09 +0200 Subject: [PATCH 221/250] Load virtual filesystem on lobby start to access schemas --- lobby/EntryPoint.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lobby/EntryPoint.cpp b/lobby/EntryPoint.cpp index 48a6cd783..5cd65a24f 100644 --- a/lobby/EntryPoint.cpp +++ b/lobby/EntryPoint.cpp @@ -12,12 +12,17 @@ #include "LobbyServer.h" #include "../lib/logging/CBasicLogConfigurator.h" +#include "../lib/filesystem/CFilesystemLoader.h" +#include "../lib/filesystem/Filesystem.h" #include "../lib/VCMIDirs.h" static const int LISTENING_PORT = 30303; int main(int argc, const char * argv[]) { + CResourceHandler::initialize(); + CResourceHandler::load("config/filesystem.json"); // FIXME: we actually need only config directory for schemas, can be reduced + #ifndef VCMI_IOS console = new CConsoleHandler(); #endif From c21e5bb0fbed5d52567d730bdc8425da463177e8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 8 Mar 2024 14:23:08 +0200 Subject: [PATCH 222/250] Always validate messages in debug mode. Fixes for schemas --- client/globalLobby/GlobalLobbyClient.cpp | 12 +++++++++--- config/schemas/lobbyProtocol/activateGameRoom.json | 2 +- config/schemas/lobbyProtocol/chatMessage.json | 7 ++++++- config/schemas/lobbyProtocol/clientRegister.json | 2 +- config/schemas/lobbyProtocol/sendChatMessage.json | 2 +- lib/json/JsonValidator.cpp | 1 - lobby/LobbyServer.cpp | 9 ++++++--- server/GlobalLobbyProcessor.cpp | 7 ++++--- 8 files changed, 28 insertions(+), 14 deletions(-) diff --git a/client/globalLobby/GlobalLobbyClient.cpp b/client/globalLobby/GlobalLobbyClient.cpp index 7a80a690b..eae444fd0 100644 --- a/client/globalLobby/GlobalLobbyClient.cpp +++ b/client/globalLobby/GlobalLobbyClient.cpp @@ -19,11 +19,13 @@ #include "../windows/InfoWindows.h" #include "../CServerHandler.h" #include "../mainmenu/CMainMenu.h" +#include "../CGameInfo.h" #include "../../lib/CConfigHandler.h" #include "../../lib/MetaString.h" #include "../../lib/json/JsonUtils.h" #include "../../lib/TextOperations.h" +#include "../../lib/CGeneralTextHandler.h" GlobalLobbyClient::GlobalLobbyClient() = default; GlobalLobbyClient::~GlobalLobbyClient() = default; @@ -119,7 +121,7 @@ void GlobalLobbyClient::receiveLoginSuccess(const JsonNode & json) if(!loginWindowPtr || !GH.windows().topWindow()) throw std::runtime_error("lobby connection finished without active login window!"); - loginWindowPtr->onConnectionSuccess(); + loginWindowPtr->onLoginSuccess(); } void GlobalLobbyClient::receiveChatHistory(const JsonNode & json) @@ -231,6 +233,8 @@ void GlobalLobbyClient::sendClientRegister(const std::string & accountName) JsonNode toSend; toSend["type"].String() = "clientRegister"; toSend["displayName"].String() = accountName; + toSend["language"].String() = CGI->generaltexth->getPreferredLanguage(); + toSend["version"].String() = VCMI_VERSION_STRING; sendMessage(toSend); } @@ -240,6 +244,8 @@ void GlobalLobbyClient::sendClientLogin() toSend["type"].String() = "clientLogin"; toSend["accountID"] = settings["lobby"]["accountID"]; toSend["accountCookie"] = settings["lobby"]["accountCookie"]; + toSend["language"].String() = CGI->generaltexth->getPreferredLanguage(); + toSend["version"].String() = VCMI_VERSION_STRING; sendMessage(toSend); } @@ -274,7 +280,7 @@ void GlobalLobbyClient::onDisconnected(const std::shared_ptr void GlobalLobbyClient::sendMessage(const JsonNode & data) { - assert(JsonUtils::validate(data, "vcmi:lobbyProtocol/" + data["type"].String(), "network")); + assert(JsonUtils::validate(data, "vcmi:lobbyProtocol/" + data["type"].String(), data["type"].String() + " pack")); networkConnection->sendPacket(data.toBytes()); } @@ -364,6 +370,6 @@ void GlobalLobbyClient::sendProxyConnectionLogin(const NetworkConnectionPtr & ne toSend["accountCookie"] = settings["lobby"]["accountCookie"]; toSend["gameRoomID"] = settings["lobby"]["roomID"]; - assert(JsonUtils::validate(toSend, "vcmi:lobbyProtocol/" + toSend["type"].String(), "network")); + assert(JsonUtils::validate(toSend, "vcmi:lobbyProtocol/" + toSend["type"].String(), toSend["type"].String() + " pack")); netConnection->sendPacket(toSend.toBytes()); } diff --git a/config/schemas/lobbyProtocol/activateGameRoom.json b/config/schemas/lobbyProtocol/activateGameRoom.json index 08b1f995d..e001568d7 100644 --- a/config/schemas/lobbyProtocol/activateGameRoom.json +++ b/config/schemas/lobbyProtocol/activateGameRoom.json @@ -3,7 +3,7 @@ "$schema" : "http://json-schema.org/draft-06/schema", "title" : "Lobby protocol: activateGameRoom", "description" : "Sent by client when player wants to activate a game room", - "required" : [ "accountID", "accountCookie", "language", "version" ], + "required" : [ "type", "hostAccountID", "roomType" ], "additionalProperties" : false, "properties" : { diff --git a/config/schemas/lobbyProtocol/chatMessage.json b/config/schemas/lobbyProtocol/chatMessage.json index 825edf43c..b0b224c03 100644 --- a/config/schemas/lobbyProtocol/chatMessage.json +++ b/config/schemas/lobbyProtocol/chatMessage.json @@ -22,10 +22,15 @@ "type" : "string", "description" : "ID of account that have sent this message" }, + "displayName" : + { + "type" : "string", + "description" : "Display name of account that have sent this message" + }, "roomMode" : { "type" : "string", - "const" : "general", + "const" : "global", "description" : "Type of room to which this message has been set. Right now can only be 'general'" }, "roomName" : diff --git a/config/schemas/lobbyProtocol/clientRegister.json b/config/schemas/lobbyProtocol/clientRegister.json index c0dc3a716..58448a809 100644 --- a/config/schemas/lobbyProtocol/clientRegister.json +++ b/config/schemas/lobbyProtocol/clientRegister.json @@ -10,7 +10,7 @@ "type" : { "type" : "string", - "const" : "clientLogin" + "const" : "clientRegister" }, "displayName" : { diff --git a/config/schemas/lobbyProtocol/sendChatMessage.json b/config/schemas/lobbyProtocol/sendChatMessage.json index 6de400ae4..77142f38b 100644 --- a/config/schemas/lobbyProtocol/sendChatMessage.json +++ b/config/schemas/lobbyProtocol/sendChatMessage.json @@ -3,7 +3,7 @@ "$schema" : "http://json-schema.org/draft-06/schema", "title" : "Lobby protocol: sendChatMessage", "description" : "Sent by client when player requests lobby login", - "required" : [ "accountID", "accountCookie", "language", "version" ], + "required" : [ "type", "messageText" ], "additionalProperties" : false, "properties" : { diff --git a/lib/json/JsonValidator.cpp b/lib/json/JsonValidator.cpp index 0cb33f937..e2134c0f8 100644 --- a/lib/json/JsonValidator.cpp +++ b/lib/json/JsonValidator.cpp @@ -522,7 +522,6 @@ JsonValidator::TValidatorMap createCommonFields() // Not implemented ret["propertyNames"] = notImplementedCheck; ret["contains"] = notImplementedCheck; - ret["const"] = notImplementedCheck; ret["examples"] = notImplementedCheck; return ret; diff --git a/lobby/LobbyServer.cpp b/lobby/LobbyServer.cpp index 88683e4bc..7696a0d9d 100644 --- a/lobby/LobbyServer.cpp +++ b/lobby/LobbyServer.cpp @@ -60,7 +60,7 @@ NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) c void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json) { - assert(JsonUtils::validate(json, "vcmi:lobbyProtocol/" + json["type"].String(), "network")); + assert(JsonUtils::validate(json, "vcmi:lobbyProtocol/" + json["type"].String(), json["type"].String() + " pack")); target->sendPacket(json.toBytes()); } @@ -104,6 +104,7 @@ void LobbyServer::sendChatHistory(const NetworkConnectionPtr & target, const std { JsonNode reply; reply["type"].String() = "chatHistory"; + reply["messages"].Vector(); // force creation of empty vector for(const auto & message : boost::adaptors::reverse(history)) { @@ -126,6 +127,7 @@ void LobbyServer::broadcastActiveAccounts() JsonNode reply; reply["type"].String() = "activeAccounts"; + reply["accounts"].Vector(); // force creation of empty vector for(const auto & account : activeAccountsStats) { @@ -145,6 +147,7 @@ JsonNode LobbyServer::prepareActiveGameRooms() auto activeGameRoomStats = database->getActiveGameRooms(); JsonNode reply; reply["type"].String() = "activeGameRooms"; + reply["gameRooms"].Vector(); // force creation of empty vector for(const auto & gameRoom : activeGameRoomStats) { @@ -262,7 +265,7 @@ JsonNode LobbyServer::parseAndValidateMessage(const std::vector & mes std::string schemaName = "vcmi:lobbyProtocol/" + messageType; - if (!JsonUtils::validate(json, schemaName, "network")) + if (!JsonUtils::validate(json, schemaName, messageType + " pack")) { logGlobal->info("Json validation error encountered!"); assert(0); @@ -369,7 +372,7 @@ 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 sendOperationFailed(connection, "Illegal account name"); if(database->isAccountNameExists(displayName)) diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index ca6fb6828..34b9a7c6f 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -47,7 +47,7 @@ void GlobalLobbyProcessor::onDisconnected(const std::shared_ptrsendPacket(message.toBytes()); break; } @@ -125,8 +125,9 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptrsendPacket(toSend.toBytes()); } else @@ -143,7 +144,7 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptrsendPacket(toSend.toBytes()); proxyConnections[guestAccountID] = connection; From 87bf4b752a52fbadb0415243e657c5bd8476b7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 9 Mar 2024 07:38:11 +0100 Subject: [PATCH 223/250] Better implementation of granting multiple rewards --- lib/mapObjects/CRewardableObject.cpp | 32 ++++++++++------------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 314a9bd7a..3590d3f38 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -55,33 +55,17 @@ void CRewardableObject::selectRewardWthMessage(const CGHeroInstance * contextHer void CRewardableObject::grantAllRewardsWthMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndices, bool markAsVisit) const { - // TODO: A single message for all rewards? if (rewardIndices.empty()) return; - - auto index = rewardIndices.front(); - auto vi = configuration.info.at(index); - - logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString()); - // show message only if it is not empty or in infobox - if (configuration.infoWindowType != EInfoWindowMode::MODAL || !vi.message.toString().empty()) - { - InfoWindow iw; - iw.player = contextHero->tempOwner; - iw.text = vi.message; - iw.components = loadComponents(contextHero, rewardIndices); - iw.type = configuration.infoWindowType; - if(!iw.components.empty() || !iw.text.toString().empty()) - cb->showInfoDialog(&iw); - } - // grant reward afterwards. Note that it may remove object - if(markAsVisit) - markAsVisited(contextHero); for (auto index : rewardIndices) { - grantReward(index, contextHero); + // TODO: Allow a single message for multiple / all rewards? + grantRewardWithMessage(contextHero, index, false); } + // Mark visited only after all rewards were processed + if(markAsVisit) + markAsVisited(contextHero); } std::vector CRewardableObject::loadComponents(const CGHeroInstance * contextHero, const std::vector & rewardIndices) const @@ -157,6 +141,12 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const grantRewardWithMessage(h, rewardIndex, true); break; } + case Rewardable::SELECT_ALL: // grant all possible + { + auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT); + grantAllRewardsWthMessage(h, rewards, true); + break; + } } break; } From 69cfc83be313c8653e86f1d6e3c1bf0201274dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 9 Mar 2024 08:17:46 +0100 Subject: [PATCH 224/250] Fix issue found by Sonarcloud --- lib/mapObjects/CRewardableObject.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 3590d3f38..03c7e91ad 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -142,11 +142,8 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const break; } case Rewardable::SELECT_ALL: // grant all possible - { - auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT); grantAllRewardsWthMessage(h, rewards, true); break; - } } break; } From 43a1e691e45b2fd0a15dd8aeae857d435a8e7353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 9 Mar 2024 10:17:30 +0100 Subject: [PATCH 225/250] Trying to satisfy Sonarcloud --- mapeditor/inspector/rewardswidget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index 373588a3c..67714994c 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -34,10 +34,10 @@ RewardsWidget::RewardsWidget(CMap & m, CRewardableObject & p, QWidget *parent) : //fill core elements for(const auto & s : Rewardable::VisitModeString) - ui->visitMode->addItem(QString::fromStdString(s)); + ui->selectMode->addItem(QString::fromUtf8(s.data(), s.size())); for(const auto & s : Rewardable::SelectModeString) - ui->selectMode->addItem(QString::fromStdString(s)); + ui->selectMode->addItem(QString::fromUtf8(s.data(), s.size())); for(auto s : {"AUTO", "MODAL", "INFO"}) ui->windowMode->addItem(QString::fromStdString(s)); From 8ef7daf45a603fe00a94cfd933af0e5407b021e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 9 Mar 2024 11:02:43 +0100 Subject: [PATCH 226/250] Missing change --- lib/rewardable/Configuration.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rewardable/Configuration.h b/lib/rewardable/Configuration.h index 8815c4640..9880ea547 100644 --- a/lib/rewardable/Configuration.h +++ b/lib/rewardable/Configuration.h @@ -47,8 +47,8 @@ enum class EEventType EVENT_NOT_AVAILABLE }; -const std::array SelectModeString{"selectFirst", "selectPlayer", "selectRandom", "selectAll"}; -const std::array VisitModeString{"unlimited", "once", "hero", "bonus", "limiter", "player"}; +constexpr std::array SelectModeString{"selectFirst", "selectPlayer", "selectRandom", "selectAll"}; +constexpr std::array VisitModeString{"unlimited", "once", "hero", "bonus", "limiter", "player"}; struct DLL_LINKAGE ResetInfo { From bec2c0cac281a15e9e7dc0f4d1faaf972108b136 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 9 Mar 2024 16:20:00 +0200 Subject: [PATCH 227/250] NKAI: fix sonar and refactoring --- .../Analyzers/DangerHitMapAnalyzer.cpp | 10 +- AI/Nullkiller/Analyzers/ObjectClusterizer.cpp | 262 +++++++++--------- AI/Nullkiller/Analyzers/ObjectClusterizer.h | 5 + AI/Nullkiller/Pathfinding/AINodeStorage.h | 4 +- AI/Nullkiller/Pathfinding/AIPathfinder.cpp | 19 +- AI/Nullkiller/Pathfinding/AIPathfinder.h | 4 +- AI/Nullkiller/Pathfinding/ObjectGraph.cpp | 237 +++++++++------- AI/Nullkiller/Pathfinding/ObjectGraph.h | 2 + .../Rules/AIMovementToDestinationRule.cpp | 2 +- 9 files changed, 286 insertions(+), 259 deletions(-) diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index 0096a466d..84160954a 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -157,12 +157,13 @@ void DangerHitMapAnalyzer::calculateTileOwners() if(hitMap.shape()[0] != mapSize.x || hitMap.shape()[1] != mapSize.y || hitMap.shape()[2] != mapSize.z) hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]); - std::map townHeroes; + std::vector> temporaryHeroes; std::map heroTownMap; + std::map townHeroes; auto addTownHero = [&](const CGTownInstance * town) { - auto townHero = new CGHeroInstance(town->cb); + auto townHero = temporaryHeroes.emplace_back(std::make_unique(town->cb)).get(); CRandomGenerator rng; auto visitablePos = town->visitablePos(); @@ -238,11 +239,6 @@ void DangerHitMapAnalyzer::calculateTileOwners() hitMap[pos.x][pos.y][pos.z].closestTown = enemyTown; } }); - - for(auto h : townHeroes) - { - delete h.first; - } } const std::vector & DangerHitMapAnalyzer::getTownThreats(const CGTownInstance * town) const diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 908f6c6cd..b294b228e 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -189,15 +189,7 @@ bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const return true; //all of the following is met } -void ObjectClusterizer::clusterize() -{ - auto start = std::chrono::high_resolution_clock::now(); - - nearObjects.reset(); - farObjects.reset(); - blockedObjects.clear(); - - Obj ignoreObjects[] = { +Obj ObjectClusterizer::IgnoredObjectTypes[] = { Obj::BOAT, Obj::EYE_OF_MAGI, Obj::MONOLITH_ONE_WAY_ENTRANCE, @@ -216,7 +208,15 @@ void ObjectClusterizer::clusterize() Obj::REDWOOD_OBSERVATORY, Obj::CARTOGRAPHER, Obj::PILLAR_OF_FIRE - }; +}; + +void ObjectClusterizer::clusterize() +{ + auto start = std::chrono::high_resolution_clock::now(); + + nearObjects.reset(); + farObjects.reset(); + blockedObjects.clear(); logAi->debug("Begin object clusterization"); @@ -225,137 +225,18 @@ void ObjectClusterizer::clusterize() ai->memory->visitableObjs.end()); #if NKAI_TRACE_LEVEL == 0 - parallel_for(blocked_range(0, objs.size()), [&](const blocked_range & r) + parallel_for(blocked_range(0, objs.size()), [&](const blocked_range & r) { #else blocked_range r(0, objs.size()); #endif - { auto priorityEvaluator = ai->priorityEvaluators->acquire(); for(int i = r.begin(); i != r.end(); i++) { - auto obj = objs[i]; - - if(!shouldVisitObject(obj)) - { -#if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Skip object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); -#endif - continue; - } - -#if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); -#endif - - auto paths = ai->pathfinder->getPathInfo(obj->visitablePos(), true); - - if(paths.empty()) - { -#if NKAI_TRACE_LEVEL >= 2 - logAi->trace("No paths found."); -#endif - continue; - } - - std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool - { - return p1.movementCost() < p2.movementCost(); - }); - - if(vstd::contains(ignoreObjects, obj->ID)) - { - farObjects.addObject(obj, paths.front(), 0); - -#if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString()); -#endif - - continue; - } - - std::set heroesProcessed; - - for(auto & path : paths) - { -#if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Checking path %s", path.toString()); -#endif - - if(!shouldVisit(ai, path.targetHero, obj)) - { -#if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Hero %s does not need to visit %s", path.targetHero->getObjectName(), obj->getObjectName()); -#endif - continue; - } - - if(path.nodes.size() > 1) - { - auto blocker = getBlocker(path); - - if(blocker) - { - if(vstd::contains(heroesProcessed, path.targetHero)) - { -#if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Hero %s is already processed.", path.targetHero->getObjectName()); -#endif - continue; - } - - heroesProcessed.insert(path.targetHero); - - float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); - - if(priority < MIN_PRIORITY) - continue; - - ClusterMap::accessor cluster; - blockedObjects.insert( - cluster, - ClusterMap::value_type(blocker, std::make_shared(blocker))); - - cluster->second->addObject(obj, path, priority); - -#if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString()); -#endif - continue; - } - } - - heroesProcessed.insert(path.targetHero); - - float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); - - if(priority < MIN_PRIORITY) - continue; - - bool interestingObject = path.turn() <= 2 || priority > 0.5f; - - if(interestingObject) - { - nearObjects.addObject(obj, path, priority); - } - else - { - farObjects.addObject(obj, path, priority); - } - -#if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f", - path.toString(), - interestingObject ? "near" : "far", - path.turn(), - priority); -#endif - } + clusterizeObject(objs[i], priorityEvaluator.get()); } #if NKAI_TRACE_LEVEL == 0 }); -#else - } #endif logAi->trace("Near objects count: %i", nearObjects.objects.size()); @@ -376,4 +257,123 @@ void ObjectClusterizer::clusterize() logAi->trace("Clusterization complete in %ld", timeElapsed(start)); } +void ObjectClusterizer::clusterizeObject(const CGObjectInstance * obj, PriorityEvaluator * priorityEvaluator) +{ + if(!shouldVisitObject(obj)) + { +#if NKAI_TRACE_LEVEL >= 2 + logAi->trace("Skip object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); +#endif + return; + } + +#if NKAI_TRACE_LEVEL >= 2 + logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); +#endif + + auto paths = ai->pathfinder->getPathInfo(obj->visitablePos(), true); + + if(paths.empty()) + { +#if NKAI_TRACE_LEVEL >= 2 + logAi->trace("No paths found."); +#endif + return; + } + + std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool + { + return p1.movementCost() < p2.movementCost(); + }); + + if(vstd::contains(IgnoredObjectTypes, obj->ID)) + { + farObjects.addObject(obj, paths.front(), 0); + +#if NKAI_TRACE_LEVEL >= 2 + logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString()); +#endif + + return; + } + + std::set heroesProcessed; + + for(auto & path : paths) + { +#if NKAI_TRACE_LEVEL >= 2 + logAi->trace("Checking path %s", path.toString()); +#endif + + if(!shouldVisit(ai, path.targetHero, obj)) + { +#if NKAI_TRACE_LEVEL >= 2 + logAi->trace("Hero %s does not need to visit %s", path.targetHero->getObjectName(), obj->getObjectName()); +#endif + continue; + } + + if(path.nodes.size() > 1) + { + auto blocker = getBlocker(path); + + if(blocker) + { + if(vstd::contains(heroesProcessed, path.targetHero)) + { +#if NKAI_TRACE_LEVEL >= 2 + logAi->trace("Hero %s is already processed.", path.targetHero->getObjectName()); +#endif + continue; + } + + heroesProcessed.insert(path.targetHero); + + float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); + + if(priority < MIN_PRIORITY) + continue; + + ClusterMap::accessor cluster; + blockedObjects.insert( + cluster, + ClusterMap::value_type(blocker, std::make_shared(blocker))); + + cluster->second->addObject(obj, path, priority); + +#if NKAI_TRACE_LEVEL >= 2 + logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString()); +#endif + continue; + } + } + + heroesProcessed.insert(path.targetHero); + + float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); + + if(priority < MIN_PRIORITY) + continue; + + bool interestingObject = path.turn() <= 2 || priority > 0.5f; + + if(interestingObject) + { + nearObjects.addObject(obj, path, priority); + } + else + { + farObjects.addObject(obj, path, priority); + } + +#if NKAI_TRACE_LEVEL >= 2 + logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f", + path.toString(), + interestingObject ? "near" : "far", + path.turn(), + priority); +#endif + } +} + } diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.h b/AI/Nullkiller/Analyzers/ObjectClusterizer.h index bc4118917..56754a416 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.h +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.h @@ -49,9 +49,13 @@ public: using ClusterMap = tbb::concurrent_hash_map>; +class PriorityEvaluator; + class ObjectClusterizer { private: + static Obj IgnoredObjectTypes[]; + ObjectCluster nearObjects; ObjectCluster farObjects; ClusterMap blockedObjects; @@ -68,6 +72,7 @@ public: private: bool shouldVisitObject(const CGObjectInstance * obj) const; + void clusterizeObject(const CGObjectInstance * obj, PriorityEvaluator * priorityEvaluator); }; } diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 818ded9c8..893e15633 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -11,8 +11,8 @@ #pragma once #define NKAI_PATHFINDER_TRACE_LEVEL 0 -#define NKAI_GRAPH_TRACE_LEVEL 0 -#define NKAI_TRACE_LEVEL 1 +constexpr int NKAI_GRAPH_TRACE_LEVEL = 0; +#define NKAI_TRACE_LEVEL 0 #include "../../../lib/pathfinder/CGPathNode.h" #include "../../../lib/pathfinder/INodeStorage.h" diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp index 10d7f9644..50ed296ef 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp @@ -58,7 +58,7 @@ std::vector AIPathfinder::getPathInfo(const int3 & tile, bool includeGra return info; } -void AIPathfinder::updatePaths(std::map heroes, PathfinderSettings pathfinderSettings) +void AIPathfinder::updatePaths(const std::map & heroes, PathfinderSettings pathfinderSettings) { if(!storage) { @@ -125,7 +125,7 @@ void AIPathfinder::updatePaths(std::map heroes logAi->trace("Recalculated paths in %ld", timeElapsed(start)); } -void AIPathfinder::updateGraphs(std::map heroes) +void AIPathfinder::updateGraphs(const std::map & heroes) { auto start = std::chrono::high_resolution_clock::now(); std::vector heroesVector; @@ -134,22 +134,23 @@ void AIPathfinder::updateGraphs(std::map heroe for(auto hero : heroes) { - heroGraphs.emplace(hero.first->id, GraphPaths()); - heroesVector.push_back(hero.first); + if(heroGraphs.try_emplace(hero.first->id, GraphPaths()).second) + heroesVector.push_back(hero.first); } - parallel_for(blocked_range(0, heroesVector.size()), [&](const blocked_range & r) + parallel_for(blocked_range(0, heroesVector.size()), [this, &heroesVector](const blocked_range & r) { for(auto i = r.begin(); i != r.end(); i++) heroGraphs.at(heroesVector[i]->id).calculatePaths(heroesVector[i], ai); }); -#if NKAI_GRAPH_TRACE_LEVEL >= 1 - for(auto hero : heroes) + if(NKAI_GRAPH_TRACE_LEVEL >= 1) { - heroGraphs[hero.first->id].dumpToLog(); + for(auto hero : heroes) + { + heroGraphs[hero.first->id].dumpToLog(); + } } -#endif logAi->trace("Graph paths updated in %lld", timeElapsed(start)); } diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.h b/AI/Nullkiller/Pathfinding/AIPathfinder.h index 07e3c5e6a..ed6c5b5ed 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.h +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.h @@ -46,8 +46,8 @@ public: AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai); std::vector getPathInfo(const int3 & tile, bool includeGraph = false) const; bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const; - void updatePaths(std::map heroes, PathfinderSettings pathfinderSettings); - void updateGraphs(std::map heroes); + void updatePaths(const std::map & heroes, PathfinderSettings pathfinderSettings); + void updateGraphs(const std::map & heroes); void init(); std::shared_ptrgetStorage() diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp index 6182703f6..5cddf022b 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp @@ -19,17 +19,100 @@ namespace NKAI { -void ObjectGraph::updateGraph(const Nullkiller * ai) +class ObjectGraphCalculator { - auto cb = ai->cb; +private: + ObjectGraph * target; + const Nullkiller * ai; std::map actors; std::map actorObjectMap; - std::vector boats; - auto addObjectActor = [&](const CGObjectInstance * obj) + std::vector> temporaryBoats; + std::vector> temporaryActorHeroes; + +public: + ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai) + :ai(ai), target(target) { - auto objectActor = new CGHeroInstance(obj->cb); + for(auto obj : ai->memory->visitableObjs) + { + if(obj && obj->isVisitable() && obj->ID != Obj::HERO) + { + addObjectActor(obj); + } + } + + for(auto town : ai->cb->getTownsInfo()) + { + addObjectActor(town); + } + + PathfinderSettings ps; + + ps.mainTurnDistanceLimit = 5; + ps.scoutTurnDistanceLimit = 1; + ps.allowBypassObjects = false; + + ai->pathfinder->updatePaths(actors, ps); + } + + void calculateConnections(const int3 & pos) + { + auto guarded = ai->cb->getGuardingCreaturePosition(pos).valid(); + + if(guarded) + return; + + auto paths = ai->pathfinder->getPathInfo(pos); + + for(AIPath & path1 : paths) + { + for(AIPath & path2 : paths) + { + if(path1.targetHero == path2.targetHero) + continue; + + auto obj1 = actorObjectMap[path1.targetHero]; + auto obj2 = actorObjectMap[path2.targetHero]; + + auto tile1 = cb->getTile(obj1->visitablePos()); + auto tile2 = cb->getTile(obj2->visitablePos()); + + if(tile2->isWater() && !tile1->isWater()) + { + auto linkTile = cb->getTile(pos); + + if(!linkTile->isWater() || obj1->ID != Obj::BOAT || obj1->ID != Obj::SHIPYARD) + continue; + } + + auto danger = ai->pathfinder->getStorage()->evaluateDanger(obj2->visitablePos(), path1.targetHero, true); + + auto updated = target->tryAddConnection( + obj1->visitablePos(), + obj2->visitablePos(), + path1.movementCost() + path2.movementCost(), + danger); + + if(NKAI_GRAPH_TRACE_LEVEL >= 2 && updated) + { + logAi->trace( + "Connected %s[%s] -> %s[%s] through [%s], cost %2f", + obj1->getObjectName(), obj1->visitablePos().toString(), + obj2->getObjectName(), obj2->visitablePos().toString(), + pos.toString(), + path1.movementCost() + path2.movementCost()); + } + } + } + } + +private: + void addObjectActor(const CGObjectInstance * obj) + { + auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique(obj->cb)).get(); + CRandomGenerator rng; auto visitablePos = obj->visitablePos(); @@ -40,105 +123,42 @@ void ObjectGraph::updateGraph(const Nullkiller * ai) if(cb->getTile(visitablePos)->isWater()) { - boats.push_back(new CGBoat(objectActor->cb)); - objectActor->boat = boats.back(); + objectActor->boat = temporaryBoats.emplace_back(std::make_unique(objectActor->cb)).get(); } actorObjectMap[objectActor] = obj; - actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD ? HeroRole::MAIN : HeroRole::SCOUT; - addObject(obj); + actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD ? HeroRole::MAIN : HeroRole::SCOUT; + + target->addObject(obj); }; +}; - for(auto obj : ai->memory->visitableObjs) - { - if(obj && obj->isVisitable() && obj->ID != Obj::HERO) - { - addObjectActor(obj); - } - } +bool ObjectGraph::tryAddConnection( + const int3 & from, + const int3 & to, + float cost, + uint64_t danger) +{ + return nodes[from].connections[to].update(cost, danger); +} - for(auto town : cb->getTownsInfo()) - { - addObjectActor(town); - } +void ObjectGraph::updateGraph(const Nullkiller * ai) +{ + auto cb = ai->cb; - PathfinderSettings ps; - - ps.mainTurnDistanceLimit = 5; - ps.scoutTurnDistanceLimit = 1; - ps.allowBypassObjects = false; + ObjectGraphCalculator calculator(this, ai); - ai->pathfinder->updatePaths(actors, ps); - - foreach_tile_pos(cb.get(), [&](const CPlayerSpecificInfoCallback * cb, const int3 & pos) + foreach_tile_pos(cb.get(), [this, &calculator](const CPlayerSpecificInfoCallback * cb, const int3 & pos) { if(nodes.find(pos) != nodes.end()) return; - auto guarded = ai->cb->getGuardingCreaturePosition(pos).valid(); - - if(guarded) - return; - - auto paths = ai->pathfinder->getPathInfo(pos); - - for(AIPath & path1 : paths) - { - for(AIPath & path2 : paths) - { - if(path1.targetHero == path2.targetHero) - continue; - - auto obj1 = actorObjectMap[path1.targetHero]; - auto obj2 = actorObjectMap[path2.targetHero]; - - auto tile1 = cb->getTile(obj1->visitablePos()); - auto tile2 = cb->getTile(obj2->visitablePos()); - - if(tile2->isWater() && !tile1->isWater()) - { - auto linkTile = cb->getTile(pos); - - if(!linkTile->isWater() || obj1->ID != Obj::BOAT || obj1->ID != Obj::SHIPYARD) - continue; - } - - auto danger = ai->pathfinder->getStorage()->evaluateDanger(obj2->visitablePos(), path1.targetHero, true); - - auto updated = nodes[obj1->visitablePos()].connections[obj2->visitablePos()].update( - path1.movementCost() + path2.movementCost(), - danger); - -#if NKAI_GRAPH_TRACE_LEVEL >= 2 - if(updated) - { - logAi->trace( - "Connected %s[%s] -> %s[%s] through [%s], cost %2f", - obj1->getObjectName(), obj1->visitablePos().toString(), - obj2->getObjectName(), obj2->visitablePos().toString(), - pos.toString(), - path1.movementCost() + path2.movementCost()); - } -#else - (void)updated; -#endif - } - } + calculator.calculateConnections(pos); }); - for(auto h : actorObjectMap) - { - delete h.first; - } + if(NKAI_GRAPH_TRACE_LEVEL >= 1) + dumpToLog("graph"); - for(auto boat : boats) - { - delete boat; - } - -#if NKAI_GRAPH_TRACE_LEVEL >= 1 - dumpToLog("graph"); -#endif } void ObjectGraph::addObject(const CGObjectInstance * obj) @@ -199,14 +219,15 @@ void ObjectGraph::dumpToLog(std::string visualKey) const { for(auto & node : tile.second.connections) { -#if NKAI_GRAPH_TRACE_LEVEL >= 2 - logAi->trace( - "%s -> %s: %f !%d", - node.first.toString(), - tile.first.toString(), - node.second.cost, - node.second.danger); -#endif + if(NKAI_GRAPH_TRACE_LEVEL >= 2) + { + logAi->trace( + "%s -> %s: %f !%d", + node.first.toString(), + tile.first.toString(), + node.second.cost, + node.second.danger); + } logBuilder.addLine(tile.first, node.first); } @@ -278,14 +299,15 @@ void GraphPaths::dumpToLog() const if(!node.previous.valid()) continue; -#if NKAI_GRAPH_TRACE_LEVEL >= 2 - logAi->trace( - "%s -> %s: %f !%d", - node.previous.coord.toString(), - tile.first.toString(), - node.cost, - node.danger); -#endif + if(NKAI_GRAPH_TRACE_LEVEL >= 2) + { + logAi->trace( + "%s -> %s: %f !%d", + node.previous.coord.toString(), + tile.first.toString(), + node.cost, + node.danger); + } logBuilder.addLine(node.previous.coord, tile.first); } @@ -343,7 +365,7 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe auto currentNode = currentTile->second[current.nodeType]; - if(currentNode.cost == 0) + if(!currentNode.previous.valid()) break; allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE; @@ -352,7 +374,7 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe tilesToPass.push_back(current.coord); - if(currentNode.cost < 2) + if(currentNode.cost < 2.0f) break; current = currentNode.previous; @@ -377,6 +399,7 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe n.turns = static_cast(cost) + 1; // just in case lets select worst scenario n.danger = danger; n.targetHero = hero; + n.parentIndex = 0; for(auto & node : path.nodes) { diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.h b/AI/Nullkiller/Pathfinding/ObjectGraph.h index faf6fb499..4407dfc93 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraph.h +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.h @@ -76,6 +76,8 @@ public: { return nodes.at(tile); } + + bool tryAddConnection(const int3 & from, const int3 & to, float cost, uint64_t danger); }; struct GraphPathNode; diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp index 3f0acd1b2..59368e9c4 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp @@ -45,7 +45,7 @@ namespace AIPathfinding if(!allowBypassObjects) { - if (source.node->getCost() == 0) + if (source.node->getCost() < 0.0001f) return; // when actor represents moster graph node, we need to let him escape monster From a93dd25867fd60c3d0d27a42334311143434747c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Thu, 14 Mar 2024 10:22:15 +0100 Subject: [PATCH 228/250] Update docs to be in line with existing code. --- docs/modders/Map_Objects/Rewardable.md | 2 +- lib/mapObjects/CRewardableObject.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modders/Map_Objects/Rewardable.md b/docs/modders/Map_Objects/Rewardable.md index 4a62ebf1b..38785b87b 100644 --- a/docs/modders/Map_Objects/Rewardable.md +++ b/docs/modders/Map_Objects/Rewardable.md @@ -515,7 +515,7 @@ canLearnSpells ```jsonc "creatures" : [ { - "creature" : "archer", + "type" : "archer", "upgradeChance" : 30, "amount" : 20, } diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 03c7e91ad..9bc4126e7 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -60,7 +60,7 @@ void CRewardableObject::grantAllRewardsWthMessage(const CGHeroInstance * context for (auto index : rewardIndices) { - // TODO: Allow a single message for multiple / all rewards? + // TODO: Merge all rewards of same type, with single message? grantRewardWithMessage(contextHero, index, false); } // Mark visited only after all rewards were processed From 35cf227b35ec4eafc4e542d3cdb644fbc84df7ee Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Tue, 6 Feb 2024 01:27:08 +0300 Subject: [PATCH 229/250] [cmake] simplify 'share' files installation on Linux --- client/CMakeLists.txt | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 561dd0201..3f3bd22b3 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -507,16 +507,18 @@ endif() #install icons and desktop file on Linux if(NOT WIN32 AND NOT APPLE AND NOT ANDROID) #FIXME: move to client makefile? - install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.16x16.png" DESTINATION share/icons/hicolor/16x16/apps RENAME vcmiclient.png) - install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.22x22.png" DESTINATION share/icons/hicolor/22x22/apps RENAME vcmiclient.png) - install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.32x32.png" DESTINATION share/icons/hicolor/32x32/apps RENAME vcmiclient.png) - install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.48x48.png" DESTINATION share/icons/hicolor/48x48/apps RENAME vcmiclient.png) - install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.64x64.png" DESTINATION share/icons/hicolor/64x64/apps RENAME vcmiclient.png) - install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.128x128.png" DESTINATION share/icons/hicolor/128x128/apps RENAME vcmiclient.png) - install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.256x256.png" DESTINATION share/icons/hicolor/256x256/apps RENAME vcmiclient.png) - install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.512x512.png" DESTINATION share/icons/hicolor/512x512/apps RENAME vcmiclient.png) - install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.1024x1024.png" DESTINATION share/icons/hicolor/1024x1024/apps RENAME vcmiclient.png) - install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.2048x2048.png" DESTINATION share/icons/hicolor/2048x2048/apps RENAME vcmiclient.png) - install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.svg" DESTINATION share/icons/hicolor/scalable/apps RENAME vcmiclient.svg) - install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.desktop" DESTINATION share/applications) + foreach(iconSize 16 22 32 48 64 128 256 512 1024 2048) + install(FILES "icons/vcmiclient.${iconSize}x${iconSize}.png" + DESTINATION "share/icons/hicolor/${iconSize}x${iconSize}/apps" + RENAME vcmiclient.png + ) + endforeach() + + install(FILES icons/vcmiclient.svg + DESTINATION share/icons/hicolor/scalable/apps + RENAME vcmiclient.svg + ) + install(FILES icons/vcmiclient.desktop + DESTINATION share/applications + ) endif() From c210b488f3f7d25cac40c5ff3eee6a16fac6f667 Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Tue, 6 Feb 2024 01:36:35 +0300 Subject: [PATCH 230/250] [cmake] slight improvements --- CMakeLists.txt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ceadb6fd3..78122eb06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,11 +5,6 @@ cmake_minimum_required(VERSION 3.16.0) project(VCMI) # TODO -# macOS: -# - There is problem with running fixup_bundle in main project after subdirectories. -# Cmake put them after all install code of main CMakelists in cmake_install.cmake -# Currently I just added extra add_subdirectory and CMakeLists.txt in osx directory to bypass that. -# # Vckpg: # - Improve install code once there is better way to deploy DLLs and Qt plugins # @@ -23,7 +18,7 @@ project(VCMI) # - Make FindFuzzyLite check for the right version and disable FORCE_BUNDLED_FL by default if(APPLE) - if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(APPLE_MACOS 1) else() set(APPLE_IOS 1) From 74ecbec1c7f436d3ee32cfadf2197e47b7b34024 Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Tue, 6 Feb 2024 01:33:47 +0300 Subject: [PATCH 231/250] get rid of CLauncherDirs global static std::call_once causes crash --- launcher/launcherdirs.cpp | 22 ++++++++------------ launcher/launcherdirs.h | 9 +++----- launcher/main.cpp | 7 ++++++- launcher/modManager/cdownloadmanager_moc.cpp | 2 +- launcher/modManager/cmodlistview_moc.cpp | 12 +++++------ launcher/modManager/cmodmanager.cpp | 5 +++-- 6 files changed, 28 insertions(+), 29 deletions(-) diff --git a/launcher/launcherdirs.cpp b/launcher/launcherdirs.cpp index 81e474f94..84ff32a66 100644 --- a/launcher/launcherdirs.cpp +++ b/launcher/launcherdirs.cpp @@ -12,30 +12,26 @@ #include "../lib/VCMIDirs.h" -static CLauncherDirs launcherDirsGlobal; - -CLauncherDirs::CLauncherDirs() +namespace CLauncherDirs { - QDir().mkdir(downloadsPath()); - QDir().mkdir(modsPath()); +void prepare() +{ + for(auto path : {downloadsPath(), modsPath(), mapsPath()}) + QDir{}.mkdir(path); } -CLauncherDirs & CLauncherDirs::get() -{ - return launcherDirsGlobal; -} - -QString CLauncherDirs::downloadsPath() +QString downloadsPath() { return pathToQString(VCMIDirs::get().userCachePath() / "downloads"); } -QString CLauncherDirs::modsPath() +QString modsPath() { return pathToQString(VCMIDirs::get().userDataPath() / "Mods"); } -QString CLauncherDirs::mapsPath() +QString mapsPath() { return pathToQString(VCMIDirs::get().userDataPath() / "Maps"); } +} diff --git a/launcher/launcherdirs.h b/launcher/launcherdirs.h index e549fb218..a8b5ea1c4 100644 --- a/launcher/launcherdirs.h +++ b/launcher/launcherdirs.h @@ -10,14 +10,11 @@ #pragma once /// similar to lib/VCMIDirs, controls where all launcher-related data will be stored -class CLauncherDirs +namespace CLauncherDirs { -public: - CLauncherDirs(); - - static CLauncherDirs & get(); + void prepare(); QString downloadsPath(); QString modsPath(); QString mapsPath(); -}; +} diff --git a/launcher/main.cpp b/launcher/main.cpp index cb0b38ac1..7b4323cfe 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -10,11 +10,13 @@ #include "StdInc.h" #include "main.h" #include "mainwindow_moc.h" +#include "launcherdirs.h" + +#include "../lib/VCMIDirs.h" #include #include #include -#include "../lib/VCMIDirs.h" // Conan workaround https://github.com/conan-io/conan-center-index/issues/13332 #ifdef VCMI_IOS @@ -33,8 +35,11 @@ int main(int argc, char * argv[]) #endif QApplication vcmilauncher(argc, argv); + CLauncherDirs::prepare(); + MainWindow mainWindow; mainWindow.show(); + result = vcmilauncher.exec(); #ifdef VCMI_IOS } diff --git a/launcher/modManager/cdownloadmanager_moc.cpp b/launcher/modManager/cdownloadmanager_moc.cpp index 563ce0b7e..c7960dc8e 100644 --- a/launcher/modManager/cdownloadmanager_moc.cpp +++ b/launcher/modManager/cdownloadmanager_moc.cpp @@ -22,7 +22,7 @@ void CDownloadManager::downloadFile(const QUrl & url, const QString & file, qint { QNetworkRequest request(url); FileEntry entry; - entry.file.reset(new QFile(CLauncherDirs::get().downloadsPath() + '/' + file)); + entry.file.reset(new QFile(QString{QLatin1String{"%1/%2"}}.arg(CLauncherDirs::downloadsPath(), file))); entry.bytesReceived = 0; entry.totalSize = bytesTotal; entry.filename = file; diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index 528c448cc..347091803 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -841,7 +841,7 @@ void CModListView::installMods(QStringList archives) void CModListView::installMaps(QStringList maps) { - QString destDir = CLauncherDirs::get().mapsPath() + "/"; + const auto destDir = CLauncherDirs::mapsPath() + QChar{'/'}; for(QString map : maps) { @@ -890,18 +890,18 @@ void CModListView::loadScreenshots() QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString(); assert(modModel->hasMod(modName)); //should be filtered out by check above - for(QString & url : modModel->getMod(modName).getValue("screenshots").toStringList()) + for(QString url : modModel->getMod(modName).getValue("screenshots").toStringList()) { // URL must be encoded to something else to get rid of symbols illegal in file names - auto hashed = QCryptographicHash::hash(url.toUtf8(), QCryptographicHash::Md5); - auto hashedStr = QString::fromUtf8(hashed.toHex()); + const auto hashed = QCryptographicHash::hash(url.toUtf8(), QCryptographicHash::Md5); + const auto fileName = QString{QLatin1String{"%1.png"}}.arg(QLatin1String{hashed.toHex()}); - QString fullPath = CLauncherDirs::get().downloadsPath() + '/' + hashedStr + ".png"; + const auto fullPath = QString{QLatin1String{"%1/%2"}}.arg(CLauncherDirs::downloadsPath(), fileName); QPixmap pixmap(fullPath); if(pixmap.isNull()) { // image file not exists or corrupted - try to redownload - downloadFile(hashedStr + ".png", url, "screenshots"); + downloadFile(fileName, url, "screenshots"); } else { diff --git a/launcher/modManager/cmodmanager.cpp b/launcher/modManager/cmodmanager.cpp index 959a429c5..d50747e73 100644 --- a/launcher/modManager/cmodmanager.cpp +++ b/launcher/modManager/cmodmanager.cpp @@ -271,7 +271,7 @@ bool CModManager::doEnableMod(QString mod, bool on) bool CModManager::doInstallMod(QString modname, QString archivePath) { - QString destDir = CLauncherDirs::get().modsPath() + "/"; + const auto destDir = CLauncherDirs::modsPath() + QChar{'/'}; if(!QFile(archivePath).exists()) return addError(modname, "Mod archive is missing"); @@ -288,10 +288,11 @@ bool CModManager::doInstallMod(QString modname, QString archivePath) auto futureExtract = std::async(std::launch::async, [&archivePath, &destDir, &filesCounter, &filesToExtract]() { + const auto destDirFsPath = qstringToPath(destDir); ZipArchive archive(qstringToPath(archivePath)); for (auto const & file : filesToExtract) { - if (!archive.extract(qstringToPath(destDir), file)) + if (!archive.extract(destDirFsPath, file)) return false; ++filesCounter; } From 9e7f4e604a8545f5d5c8022cd2a71805fd6bd48e Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Tue, 6 Feb 2024 01:34:48 +0300 Subject: [PATCH 232/250] fix indentation --- Global.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Global.h b/Global.h index 2b6b66be5..5fa4eae5a 100644 --- a/Global.h +++ b/Global.h @@ -215,10 +215,10 @@ using TLockGuardRec = std::lock_guard; /* ---------------------------------------------------------------------------- */ // Import + Export macro declarations #ifdef VCMI_WINDOWS -#ifdef VCMI_DLL_STATIC +# ifdef VCMI_DLL_STATIC # define DLL_IMPORT # define DLL_EXPORT -#elif defined(__GNUC__) +# elif defined(__GNUC__) # define DLL_IMPORT __attribute__((dllimport)) # define DLL_EXPORT __attribute__((dllexport)) # else From 006ec227ce3335e3d372bda6fa87dd28606d85ae Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 16 Mar 2024 12:29:36 +0300 Subject: [PATCH 233/250] [iOS] fix crash on starting game apparently iOS can't change socket buffer size --- lib/network/NetworkConnection.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index 7b31ca04b..fe10bd6f3 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -17,8 +17,27 @@ NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, cons , 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)); + + // iOS throws exception on attempt to set buffer size + constexpr auto bufferSize = 4 * 1024 * 1024; + + try + { + socket->set_option(boost::asio::socket_base::send_buffer_size{bufferSize}); + } + catch(const boost::system::system_error & e) + { + logNetwork->error("error setting 'send buffer size' socket option: %s", e.what()); + } + + try + { + socket->set_option(boost::asio::socket_base::receive_buffer_size{bufferSize}); + } + catch(const boost::system::system_error & e) + { + logNetwork->error("error setting 'receive buffer size' socket option: %s", e.what()); + } } void NetworkConnection::start() From 788e9093f31ee1d752a6ff4325a8479be2e88a1d Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sat, 16 Mar 2024 12:29:51 +0300 Subject: [PATCH 234/250] [iOS] fix app icon size, remove unused icon --- .../AppIcon.appiconset/Icon-App-60x60@1x.png | Bin 5738 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 22668 -> 22849 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 client/ios/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png diff --git a/client/ios/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png b/client/ios/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png deleted file mode 100644 index caaf8bc5cab5018f15419db24372c80a9fa55540..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5738 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 Lg{~?_#WwQ)S3f~L diff --git a/client/ios/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/client/ios/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 572699c5f1282cd2d9d6df9d11cddb7514469b99..c695523231e73f33e315bfdc82eca5c428ea96ae 100644 GIT binary patch literal 22849 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 z
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 HVEN>KaG Date: Wed, 20 Mar 2024 08:36:02 +0100 Subject: [PATCH 235/250] Best result so far --- lib/rmg/Zone.cpp | 19 ++++++++++++++++--- lib/rmg/modificators/TreasurePlacer.cpp | 12 +++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index ff9adb095..2102442be 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -239,7 +239,8 @@ void Zone::fractalize() //Squared float minDistance = 9 * 9; - float freeDistance = pos.z ? (10 * 10) : 6 * 6; + //float freeDistance = pos.z ? (10 * 10) : 6 * 6; + float freeDistance = pos.z ? (10 * 10) : (9 * 9); float spanFactor = (pos.z ? 0.3f : 0.45f); //Narrower passages in the Underground float marginFactor = 1.0f; @@ -258,10 +259,22 @@ void Zone::fractalize() } else //Scale with treasure density { + /* if (treasureValue > 400) { - // A quater at max density + // A quater at max density - means more free space marginFactor = (0.25f + ((std::max(0, (600 - treasureValue))) / (600.f - 400)) * 0.75f); + + } + */ + + if (treasureValue > 250) + { + // A quater at max density - means more free space + marginFactor = (0.5f + ((std::max(0, (600 - treasureValue))) / (600.f - 250)) * 0.5f); + + // Add more empty space to treasure zones + spanFactor *= (0.5f + ((std::max(0, (600 - treasureValue))) / (600.f - 250)) * 0.5f); } else if (treasureValue < 100) { @@ -334,7 +347,7 @@ void Zone::fractalize() } Lock lock(areaMutex); - //cut straight paths towards the center. A* is too slow for that. + //Connect with free areas auto areas = connectedAreas(clearedTiles, false); for(auto & area : areas) { diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 404a1ffd8..35e45fbab 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -887,7 +887,10 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) } //this is squared distance for optimization purposes - const float minDistance = std::max((125.f / totalDensity), 1.0f); + //const float minDistance = std::max((125.f / totalDensity), 1.0f); + + //Integer dvision - round down + const float minDistance = std::max((125 / totalDensity), 1.0f); size_t emergencyLoopFinish = 0; while(treasures.size() < count && emergencyLoopFinish < count) @@ -926,6 +929,9 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) } } + const size_t numberToPlace = treasures.size(); + size_t placedTreasures = 0; + for (auto& rmgObject : treasures) { const bool guarded = rmgObject.isGuarded(); @@ -971,6 +977,8 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) if (path.valid()) { + placedTreasures++; + /* //debug purposes treasureArea.unite(rmgObject.getArea()); if (guarded) @@ -980,10 +988,12 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) auto areaToBlock = rmgObject.getAccessibleArea(true) - guardedArea; treasureBlockArea.unite(areaToBlock); } + */ zone.connectPath(path); manager.placeObject(rmgObject, guarded, true); } } + logGlobal->trace("Zone %d: Placed %d out of %d treasures with average value %f", zone.getId(), placedTreasures, numberToPlace,averageValue); } } From 25a62b504f632bfbe32ce8a083041eb8f2261ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Wed, 20 Mar 2024 09:05:27 +0100 Subject: [PATCH 236/250] Perfection achieved? --- lib/rmg/Zone.cpp | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index 2102442be..81d18135a 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -259,22 +259,13 @@ void Zone::fractalize() } else //Scale with treasure density { - /* - if (treasureValue > 400) - { - // A quater at max density - means more free space - marginFactor = (0.25f + ((std::max(0, (600 - treasureValue))) / (600.f - 400)) * 0.75f); - - } - */ - if (treasureValue > 250) { // A quater at max density - means more free space - marginFactor = (0.5f + ((std::max(0, (600 - treasureValue))) / (600.f - 250)) * 0.5f); + marginFactor = (0.6f + ((std::max(0, (600 - treasureValue))) / (600.f - 250)) * 0.4f); - // Add more empty space to treasure zones - spanFactor *= (0.5f + ((std::max(0, (600 - treasureValue))) / (600.f - 250)) * 0.5f); + // Low value - dense obstacles + spanFactor *= (0.6f + ((std::max(0, (600 - treasureValue))) / (600.f - 250)) * 0.4f); } else if (treasureValue < 100) { From 662bb0d1f6dc3b690610b9efbd3dbc34cc76563f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Wed, 20 Mar 2024 10:03:26 +0100 Subject: [PATCH 237/250] Cut straight paths for better passability --- lib/rmg/Zone.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index 81d18135a..a2043f635 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -339,7 +339,7 @@ void Zone::fractalize() Lock lock(areaMutex); //Connect with free areas - auto areas = connectedAreas(clearedTiles, false); + auto areas = connectedAreas(clearedTiles, true); for(auto & area : areas) { if(dAreaFree.overlap(area)) @@ -348,7 +348,7 @@ void Zone::fractalize() auto availableArea = dAreaPossible + dAreaFree; rmg::Path path(availableArea); path.connect(dAreaFree); - auto res = path.search(area, false); + auto res = path.search(area, true); if(res.getPathArea().empty()) { dAreaPossible.subtract(area); From a8d1d72b158fdf326412cdf86ffe5cfabf72d231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Wed, 20 Mar 2024 10:12:48 +0100 Subject: [PATCH 238/250] Cleaned logs --- lib/rmg/modificators/TreasurePlacer.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 35e45fbab..6d54cf6e0 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -885,9 +885,6 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) vstd::amin(count, size * (10.f / DENSITY_CONSTANT) / (std::sqrt((float)averageValue / 10000))); } } - - //this is squared distance for optimization purposes - //const float minDistance = std::max((125.f / totalDensity), 1.0f); //Integer dvision - round down const float minDistance = std::max((125 / totalDensity), 1.0f); @@ -929,9 +926,6 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) } } - const size_t numberToPlace = treasures.size(); - size_t placedTreasures = 0; - for (auto& rmgObject : treasures) { const bool guarded = rmgObject.isGuarded(); @@ -977,7 +971,6 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) if (path.valid()) { - placedTreasures++; /* //debug purposes treasureArea.unite(rmgObject.getArea()); @@ -993,7 +986,6 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) manager.placeObject(rmgObject, guarded, true); } } - logGlobal->trace("Zone %d: Placed %d out of %d treasures with average value %f", zone.getId(), placedTreasures, numberToPlace,averageValue); } } From 68cdcb893e4d3c5f5ca79b5fccb2376a3d809e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Wed, 20 Mar 2024 12:39:07 +0100 Subject: [PATCH 239/250] Include treasure value in min distance calculation --- lib/rmg/modificators/TreasurePlacer.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 6d54cf6e0..4d6d09c21 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -872,22 +872,12 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) totalDensity += t->density; - const int DENSITY_CONSTANT = 300; + const int DENSITY_CONSTANT = 400; size_t count = (size * t->density) / DENSITY_CONSTANT; - //Assure space for lesser treasures, if there are any left const int averageValue = (t->min + t->max) / 2; - if (t != (treasureInfo.end() - 1)) - { - if (averageValue > 10000) - { - //Will surely be guarded => larger piles => less space inbetween - vstd::amin(count, size * (10.f / DENSITY_CONSTANT) / (std::sqrt((float)averageValue / 10000))); - } - } - //Integer dvision - round down - const float minDistance = std::max((125 / totalDensity), 1.0f); + const float minDistance = std::max(std::sqrt((float)t->min / 10 / totalDensity), 1.0f); size_t emergencyLoopFinish = 0; while(treasures.size() < count && emergencyLoopFinish < count) From 02fc410a98c8c10cde6aca859d508a5e7eba742b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Wed, 20 Mar 2024 13:51:16 +0100 Subject: [PATCH 240/250] Sonarcloud fixes --- lib/rmg/Zone.cpp | 1 - lib/rmg/modificators/TreasurePlacer.cpp | 9 +++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index a2043f635..eb7953a0c 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -239,7 +239,6 @@ void Zone::fractalize() //Squared float minDistance = 9 * 9; - //float freeDistance = pos.z ? (10 * 10) : 6 * 6; float freeDistance = pos.z ? (10 * 10) : (9 * 9); float spanFactor = (pos.z ? 0.3f : 0.45f); //Narrower passages in the Underground float marginFactor = 1.0f; diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 4d6d09c21..8595308e1 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -875,9 +875,7 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) const int DENSITY_CONSTANT = 400; size_t count = (size * t->density) / DENSITY_CONSTANT; - const int averageValue = (t->min + t->max) / 2; - - const float minDistance = std::max(std::sqrt((float)t->min / 10 / totalDensity), 1.0f); + const float minDistance = std::max(std::sqrt(t->min / 10.0f / totalDensity), 1.0f); size_t emergencyLoopFinish = 0; while(treasures.size() < count && emergencyLoopFinish < count) @@ -961,8 +959,7 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) if (path.valid()) { - /* - //debug purposes +#ifdef TREASURE_PLACER_LOG treasureArea.unite(rmgObject.getArea()); if (guarded) { @@ -971,7 +968,7 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) auto areaToBlock = rmgObject.getAccessibleArea(true) - guardedArea; treasureBlockArea.unite(areaToBlock); } - */ +#endif zone.connectPath(path); manager.placeObject(rmgObject, guarded, true); } From 433765714fe22633c6f0b62d46c87243910f57ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Wed, 20 Mar 2024 14:42:09 +0100 Subject: [PATCH 241/250] Add less obstacles in zones with low treasure value --- lib/rmg/Zone.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index eb7953a0c..1e3d1faca 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -269,7 +269,7 @@ void Zone::fractalize() else if (treasureValue < 100) { //Dense obstacles - spanFactor *= (treasureValue / 100.f); + spanFactor *= (0.5 + 0.5 * (treasureValue / 100.f)); vstd::amax(spanFactor, 0.15f); } if (treasureDensity <= 10) From 0c0a1bd7770bd4201d116fc7c37a939f956ec9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Wed, 20 Mar 2024 15:35:06 +0100 Subject: [PATCH 242/250] Don't scale distance of large treasures beyond 30K value --- lib/rmg/modificators/TreasurePlacer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 8595308e1..a247f72c8 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -875,7 +875,7 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) const int DENSITY_CONSTANT = 400; size_t count = (size * t->density) / DENSITY_CONSTANT; - const float minDistance = std::max(std::sqrt(t->min / 10.0f / totalDensity), 1.0f); + const float minDistance = std::max(std::sqrt(std::min(t->min, 30000) / 10.0f / totalDensity), 1.0f); size_t emergencyLoopFinish = 0; while(treasures.size() < count && emergencyLoopFinish < count) From e66ceff1548b4f4b005cc0cdfb32ec7edf55ccae Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 17 Mar 2024 09:34:54 +0200 Subject: [PATCH 243/250] NKAI: object graph improved and optimized --- .../Behaviors/CaptureObjectsBehavior.cpp | 14 +- AI/Nullkiller/Pathfinding/AINodeStorage.h | 4 +- .../Pathfinding/Actions/QuestAction.cpp | 11 +- .../Pathfinding/Actions/QuestAction.h | 2 + AI/Nullkiller/Pathfinding/ObjectGraph.cpp | 326 ++++++++++++++++-- AI/Nullkiller/Pathfinding/ObjectGraph.h | 26 +- .../Rules/AIMovementAfterDestinationRule.cpp | 19 + .../Rules/AIMovementToDestinationRule.cpp | 2 +- client/mapView/MapView.cpp | 5 + 9 files changed, 362 insertions(+), 47 deletions(-) diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index eac276ec5..66edcd0a3 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -73,13 +73,25 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector } if(objToVisit && !shouldVisit(ai->nullkiller.get(), path.targetHero, objToVisit)) + { +#if NKAI_TRACE_LEVEL >= 2 + logAi->trace("Ignore path. Hero %s should not visit obj %s", path.targetHero->getNameTranslated(), objToVisit->getObjectName()); +#endif continue; + } auto hero = path.targetHero; auto danger = path.getTotalDanger(); - if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT && path.exchangeCount > 1) + if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT + && (path.getTotalDanger() == 0 || path.turn() > 0) + && path.exchangeCount > 1) + { +#if NKAI_TRACE_LEVEL >= 2 + logAi->trace("Ignore path. Hero %s is SCOUT, chain used and no danger", path.targetHero->getNameTranslated()); +#endif continue; + } auto firstBlockedAction = path.getFirstBlockedAction(); if(firstBlockedAction) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 893e15633..a06070434 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -27,8 +27,8 @@ namespace NKAI { namespace AIPathfinding { - const int BUCKET_COUNT = 5; - const int BUCKET_SIZE = 3; + const int BUCKET_COUNT = 3; + const int BUCKET_SIZE = 5; const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE; const int CHAIN_MAX_DEPTH = 4; } diff --git a/AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp b/AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp index 1efebafca..7327ed0e5 100644 --- a/AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp +++ b/AI/Nullkiller/Pathfinding/Actions/QuestAction.cpp @@ -19,14 +19,19 @@ namespace NKAI namespace AIPathfinding { bool QuestAction::canAct(const AIPathNode * node) const + { + return canAct(node->actor->hero); + } + + bool QuestAction::canAct(const CGHeroInstance * hero) const { if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD) { - return dynamic_cast(questInfo.obj)->checkQuest(node->actor->hero); + return dynamic_cast(questInfo.obj)->checkQuest(hero); } - return questInfo.quest->activeForPlayers.count(node->actor->hero->getOwner()) - || questInfo.quest->checkQuest(node->actor->hero); + return questInfo.quest->activeForPlayers.count(hero->getOwner()) + || questInfo.quest->checkQuest(hero); } Goals::TSubgoal QuestAction::decompose(const CGHeroInstance * hero) const diff --git a/AI/Nullkiller/Pathfinding/Actions/QuestAction.h b/AI/Nullkiller/Pathfinding/Actions/QuestAction.h index 52939e5f7..da0ef66c9 100644 --- a/AI/Nullkiller/Pathfinding/Actions/QuestAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/QuestAction.h @@ -30,6 +30,8 @@ namespace AIPathfinding bool canAct(const AIPathNode * node) const override; + bool canAct(const CGHeroInstance * hero) const; + Goals::TSubgoal decompose(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override; diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp index 5cddf022b..33851e919 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp @@ -15,10 +15,18 @@ #include "../../../lib/mapping/CMap.h" #include "../Engine/Nullkiller.h" #include "../../../lib/logging/VisualLogger.h" +#include "Actions/QuestAction.h" namespace NKAI { +struct ConnectionCostInfo +{ + float totalCost = 0; + float avg = 0; + int connectionsCount = 0; +}; + class ObjectGraphCalculator { private: @@ -34,10 +42,14 @@ private: public: ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai) :ai(ai), target(target) + { + } + + void setGraphObjects() { for(auto obj : ai->memory->visitableObjs) { - if(obj && obj->isVisitable() && obj->ID != Obj::HERO) + if(obj && obj->isVisitable() && obj->ID != Obj::HERO && obj->ID != Obj::EVENT) { addObjectActor(obj); } @@ -47,7 +59,70 @@ public: { addObjectActor(town); } + } + void calculateConnections() + { + updatePaths(); + + foreach_tile_pos(ai->cb.get(), [this](const CPlayerSpecificInfoCallback * cb, const int3 & pos) + { + calculateConnections(pos); + }); + + removeExtraConnections(); + } + + void addMinimalDistanceJunctions() + { + foreach_tile_pos(ai->cb.get(), [this](const CPlayerSpecificInfoCallback * cb, const int3 & pos) + { + if(target->hasNodeAt(pos)) + return; + + if(ai->cb->getGuardingCreaturePosition(pos).valid()) + return; + + ConnectionCostInfo currentCost = getConnectionsCost(pos); + + if(currentCost.connectionsCount <= 2) + return; + + float neighborCost = currentCost.avg + 0.001f; + + if(NKAI_GRAPH_TRACE_LEVEL >= 2) + { + logAi->trace("Checking junction %s", pos.toString()); + } + + foreach_neighbour( + ai->cb.get(), + pos, + [this, &neighborCost](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor) + { + auto costTotal = this->getConnectionsCost(neighbor); + + if(costTotal.avg < neighborCost) + { + neighborCost = costTotal.avg; + + if(NKAI_GRAPH_TRACE_LEVEL >= 2) + { + logAi->trace("Better node found at %s", neighbor.toString()); + } + } + }); + + if(currentCost.avg < neighborCost) + { + addJunctionActor(pos); + } + }); + } + +private: + void updatePaths() + { PathfinderSettings ps; ps.mainTurnDistanceLimit = 5; @@ -59,11 +134,31 @@ public: void calculateConnections(const int3 & pos) { - auto guarded = ai->cb->getGuardingCreaturePosition(pos).valid(); + if(target->hasNodeAt(pos)) + { + foreach_neighbour( + ai->cb.get(), + pos, + [this, &pos](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor) + { + if(target->hasNodeAt(neighbor)) + { + auto paths = ai->pathfinder->getPathInfo(neighbor); + + for(auto & path : paths) + { + if(pos == path.targetHero->visitablePos()) + { + target->tryAddConnection(pos, neighbor, path.movementCost(), path.getTotalDanger()); + } + } + } + }); - if(guarded) return; + } + auto guardPos = ai->cb->getGuardingCreaturePosition(pos); auto paths = ai->pathfinder->getPathInfo(pos); for(AIPath & path1 : paths) @@ -73,25 +168,32 @@ public: if(path1.targetHero == path2.targetHero) continue; + auto pos1 = path1.targetHero->visitablePos(); + auto pos2 = path2.targetHero->visitablePos(); + + if(guardPos.valid() && guardPos != pos1 && guardPos != pos2) + continue; + auto obj1 = actorObjectMap[path1.targetHero]; auto obj2 = actorObjectMap[path2.targetHero]; - auto tile1 = cb->getTile(obj1->visitablePos()); - auto tile2 = cb->getTile(obj2->visitablePos()); + auto tile1 = cb->getTile(pos1); + auto tile2 = cb->getTile(pos2); if(tile2->isWater() && !tile1->isWater()) { - auto linkTile = cb->getTile(pos); + if(!cb->getTile(pos)->isWater()) + continue; - if(!linkTile->isWater() || obj1->ID != Obj::BOAT || obj1->ID != Obj::SHIPYARD) + if(obj1 && (obj1->ID != Obj::BOAT || obj1->ID != Obj::SHIPYARD)) continue; } - auto danger = ai->pathfinder->getStorage()->evaluateDanger(obj2->visitablePos(), path1.targetHero, true); + auto danger = ai->pathfinder->getStorage()->evaluateDanger(pos2, path1.targetHero, true); auto updated = target->tryAddConnection( - obj1->visitablePos(), - obj2->visitablePos(), + pos1, + pos2, path1.movementCost() + path2.movementCost(), danger); @@ -99,8 +201,8 @@ public: { logAi->trace( "Connected %s[%s] -> %s[%s] through [%s], cost %2f", - obj1->getObjectName(), obj1->visitablePos().toString(), - obj2->getObjectName(), obj2->visitablePos().toString(), + obj1 ? obj1->getObjectName() : "J", pos1.toString(), + obj2 ? obj2->getObjectName() : "J", pos2.toString(), pos.toString(), path1.movementCost() + path2.movementCost()); } @@ -108,7 +210,49 @@ public: } } -private: + bool isExtraConnection(float direct, float side1, float side2) const + { + float sideRatio = (side1 + side2) / direct; + + return sideRatio < 1.25f && direct > side1 && direct > side2; + } + + void removeExtraConnections() + { + std::vector> connectionsToRemove; + + for(auto & actor : temporaryActorHeroes) + { + auto pos = actor->visitablePos(); + auto & currentNode = target->getNode(pos); + + target->iterateConnections(pos, [this, &pos, &connectionsToRemove, ¤tNode](int3 n1, ObjectLink o1) + { + target->iterateConnections(n1, [&pos, &o1, ¤tNode, &connectionsToRemove, this](int3 n2, ObjectLink o2) + { + auto direct = currentNode.connections.find(n2); + + if(direct != currentNode.connections.end() && isExtraConnection(direct->second.cost, o1.cost, o2.cost)) + { + connectionsToRemove.push_back({pos, n2}); + } + }); + }); + } + + vstd::removeDuplicates(connectionsToRemove); + + for(auto & c : connectionsToRemove) + { + target->removeConnection(c.first, c.second); + + if(NKAI_GRAPH_TRACE_LEVEL >= 2) + { + logAi->trace("Remove ineffective connection %s->%s", c.first.toString(), c.second.toString()); + } + } + } + void addObjectActor(const CGObjectInstance * obj) { auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique(obj->cb)).get(); @@ -126,11 +270,77 @@ private: objectActor->boat = temporaryBoats.emplace_back(std::make_unique(objectActor->cb)).get(); } + assert(objectActor->visitablePos() == visitablePos); + actorObjectMap[objectActor] = obj; actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD ? HeroRole::MAIN : HeroRole::SCOUT; target->addObject(obj); - }; + } + + void addJunctionActor(const int3 & visitablePos) + { + auto internalCb = temporaryActorHeroes.front()->cb; + auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique(internalCb)).get(); + + CRandomGenerator rng; + + objectActor->setOwner(ai->playerID); // lets avoid having multiple colors + objectActor->initHero(rng, static_cast(0)); + objectActor->pos = objectActor->convertFromVisitablePos(visitablePos); + objectActor->initObj(rng); + + if(cb->getTile(visitablePos)->isWater()) + { + objectActor->boat = temporaryBoats.emplace_back(std::make_unique(objectActor->cb)).get(); + } + + assert(objectActor->visitablePos() == visitablePos); + + actorObjectMap[objectActor] = nullptr; + actors[objectActor] = HeroRole::SCOUT; + + target->registerJunction(visitablePos); + } + + ConnectionCostInfo getConnectionsCost(const int3 & pos) const + { + auto paths = ai->pathfinder->getPathInfo(pos); + std::map costs; + + for(auto & path : paths) + { + auto fromPos = path.targetHero->visitablePos(); + auto cost = costs.find(fromPos); + + if(cost == costs.end()) + { + costs.emplace(fromPos, path.movementCost()); + } + else + { + if(path.movementCost() < cost->second) + { + costs[fromPos] = path.movementCost(); + } + } + } + + ConnectionCostInfo result; + + for(auto & cost : costs) + { + result.totalCost += cost.second; + result.connectionsCount++; + } + + if(result.connectionsCount) + { + result.avg = result.totalCost / result.connectionsCount; + } + + return result; + } }; bool ObjectGraph::tryAddConnection( @@ -142,23 +352,24 @@ bool ObjectGraph::tryAddConnection( return nodes[from].connections[to].update(cost, danger); } +void ObjectGraph::removeConnection(const int3 & from, const int3 & to) +{ + nodes[from].connections.erase(to); +} + void ObjectGraph::updateGraph(const Nullkiller * ai) { auto cb = ai->cb; ObjectGraphCalculator calculator(this, ai); - foreach_tile_pos(cb.get(), [this, &calculator](const CPlayerSpecificInfoCallback * cb, const int3 & pos) - { - if(nodes.find(pos) != nodes.end()) - return; - - calculator.calculateConnections(pos); - }); + calculator.setGraphObjects(); + calculator.calculateConnections(); + calculator.addMinimalDistanceJunctions(); + calculator.calculateConnections(); if(NKAI_GRAPH_TRACE_LEVEL >= 1) dumpToLog("graph"); - } void ObjectGraph::addObject(const CGObjectInstance * obj) @@ -166,6 +377,11 @@ void ObjectGraph::addObject(const CGObjectInstance * obj) nodes[obj->visitablePos()].init(obj); } +void ObjectGraph::registerJunction(const int3 & pos) +{ + nodes[pos].initJunction(); +} + void ObjectGraph::removeObject(const CGObjectInstance * obj) { nodes[obj->visitablePos()].objectExists = false; @@ -198,6 +414,9 @@ void ObjectGraph::connectHeroes(const Nullkiller * ai) for(AIPath & path : paths) { + if(path.getFirstBlockedAction()) + continue; + auto heroPos = path.targetHero->visitablePos(); nodes[pos].connections[heroPos].update( @@ -259,20 +478,55 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil GraphPathNodePointer pos = pq.top(); pq.pop(); - auto & node = getNode(pos); + auto & node = getOrCreateNode(pos); + std::shared_ptr transitionAction; + + if(node.obj) + { + if(node.obj->ID == Obj::QUEST_GUARD + || node.obj->ID == Obj::BORDERGUARD + || node.obj->ID == Obj::BORDER_GATE) + { + auto questObj = dynamic_cast(node.obj); + auto questInfo = QuestInfo(questObj->quest, node.obj, pos.coord); + + if(node.obj->ID == Obj::QUEST_GUARD + && questObj->quest->mission == Rewardable::Limiter{} + && questObj->quest->killTarget == ObjectInstanceID::NONE) + { + continue; + } + + auto questAction = std::make_shared(questInfo); + + if(!questAction->canAct(targetHero)) + { + transitionAction = questAction; + } + } + } node.isInQueue = false; - graph.iterateConnections(pos.coord, [&](int3 target, ObjectLink o) + graph.iterateConnections(pos.coord, [this, ai, &pos, &node, &transitionAction, &pq](int3 target, ObjectLink o) { - auto targetNodeType = o.danger ? GrapthPathNodeType::BATTLE : pos.nodeType; + auto targetNodeType = o.danger || transitionAction ? GrapthPathNodeType::BATTLE : pos.nodeType; auto targetPointer = GraphPathNodePointer(target, targetNodeType); - auto & targetNode = getNode(targetPointer); + auto & targetNode = getOrCreateNode(targetPointer); if(targetNode.tryUpdate(pos, node, o)) { - if(graph.getNode(target).objTypeID == Obj::HERO) - return; + targetNode.specialAction = transitionAction; + + auto targetGraphNode = graph.getNode(target); + + if(targetGraphNode.objID.hasValue()) + { + targetNode.obj = ai->cb->getObj(targetGraphNode.objID, false); + + if(targetNode.obj && targetNode.obj->ID == Obj::HERO) + return; + } if(targetNode.isInQueue) { @@ -321,11 +575,6 @@ bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathN if(newCost < cost) { - if(nodeType < pos.nodeType) - { - logAi->error("Linking error"); - } - previous = pos; danger = prev.danger + link.danger; cost = newCost; @@ -348,7 +597,7 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe if(!node.reachable()) continue; - std::vector tilesToPass; + std::vector tilesToPass; uint64_t danger = node.danger; float cost = node.cost; @@ -372,7 +621,7 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe vstd::amax(danger, currentNode.danger); vstd::amax(cost, currentNode.cost); - tilesToPass.push_back(current.coord); + tilesToPass.push_back(current); if(currentNode.cost < 2.0f) break; @@ -383,7 +632,7 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe if(tilesToPass.empty()) continue; - auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back()); + auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back().coord); for(auto & path : entryPaths) { @@ -394,12 +643,13 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe { AIPathNodeInfo n; - n.coord = *graphTile; + n.coord = graphTile->coord; n.cost = cost; n.turns = static_cast(cost) + 1; // just in case lets select worst scenario n.danger = danger; n.targetHero = hero; - n.parentIndex = 0; + n.parentIndex = -1; + n.specialAction = getNode(*graphTile).specialAction; for(auto & node : path.nodes) { diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.h b/AI/Nullkiller/Pathfinding/ObjectGraph.h index 4407dfc93..c33ebbbdf 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraph.h +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.h @@ -50,6 +50,13 @@ struct ObjectNode objID = obj->id; objTypeID = obj->ID; } + + void initJunction() + { + objectExists = false; + objID = ObjectInstanceID(); + objTypeID = Obj(); + } }; class ObjectGraph @@ -59,8 +66,11 @@ class ObjectGraph public: void updateGraph(const Nullkiller * ai); void addObject(const CGObjectInstance * obj); + void registerJunction(const int3 & pos); void connectHeroes(const Nullkiller * ai); void removeObject(const CGObjectInstance * obj); + bool tryAddConnection(const int3 & from, const int3 & to, float cost, uint64_t danger); + void removeConnection(const int3 & from, const int3 & to); void dumpToLog(std::string visualKey) const; template @@ -77,7 +87,10 @@ public: return nodes.at(tile); } - bool tryAddConnection(const int3 & from, const int3 & to, float cost, uint64_t danger); + bool hasNodeAt(const int3 & tile) const + { + return vstd::contains(nodes, tile); + } }; struct GraphPathNode; @@ -131,6 +144,8 @@ struct GraphPathNode GraphPathNodePointer previous; float cost = BAD_COST; uint64_t danger = 0; + const CGObjectInstance * obj = nullptr; + std::shared_ptr specialAction; using TFibHeap = boost::heap::fibonacci_heap>; @@ -157,7 +172,7 @@ public: void dumpToLog() const; private: - GraphPathNode & getNode(const GraphPathNodePointer & pos) + GraphPathNode & getOrCreateNode(const GraphPathNodePointer & pos) { auto & node = pathNodes[pos.coord][pos.nodeType]; @@ -165,6 +180,13 @@ private: return node; } + + const GraphPathNode & getNode(const GraphPathNodePointer & pos) const + { + auto & node = pathNodes.at(pos.coord)[pos.nodeType]; + + return node; + } }; } diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp index 9aa5719ae..1d2e2644d 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -42,15 +42,34 @@ namespace AIPathfinding } auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper); + if(blocker == BlockingReason::NONE) { destination.blocked = nodeStorage->isDistanceLimitReached(source, destination); + if(destination.nodeObject + && !destination.blocked + && !allowBypassObjects + && !dynamic_cast(destination.nodeObject) + && destination.nodeObject->ID != Obj::EVENT) + { + destination.blocked = true; + destination.node->locked = true; + } + return; } if(!allowBypassObjects) + { + if(destination.nodeObject) + { + destination.blocked = true; + destination.node->locked = true; + } + return; + } #if NKAI_PATHFINDER_TRACE_LEVEL >= 2 logAi->trace( diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp index 59368e9c4..307cd62e6 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp @@ -49,7 +49,7 @@ namespace AIPathfinding return; // when actor represents moster graph node, we need to let him escape monster - if(!destination.guarded && cb->getGuardingCreaturePosition(source.coord) == actor->initialPosition) + if(cb->getGuardingCreaturePosition(source.coord) == actor->initialPosition) return; } diff --git a/client/mapView/MapView.cpp b/client/mapView/MapView.cpp index 008d28fc5..23ef811b1 100644 --- a/client/mapView/MapView.cpp +++ b/client/mapView/MapView.cpp @@ -71,6 +71,8 @@ public: virtual void drawLine(int3 start, int3 end) override { + const Point offset = Point(30, 30); + auto level = model->getLevel(); if(start.z != level || end.z != level) @@ -83,6 +85,9 @@ public: pStart.x += 3; pEnd.x -= 3; + pStart += offset; + pEnd += offset; + if(viewPort.isInside(pStart) && viewPort.isInside(pEnd)) { target.drawLine(pStart, pEnd, ColorRGBA(255, 255, 0), ColorRGBA(255, 0, 0)); From 05bbb458247baa43fbd43fa362bfcdbd9046637e Mon Sep 17 00:00:00 2001 From: Kris-Ja <107918765+Kris-Ja@users.noreply.github.com> Date: Sat, 23 Mar 2024 19:57:56 +0100 Subject: [PATCH 244/250] change MANA_PER_KNOWLEGDE to percentage --- config/gameConfig.json | 4 ++-- config/heroes/fortress.json | 2 +- config/heroes/inferno.json | 2 +- config/heroes/rampart.json | 2 +- config/skills.json | 2 +- docs/modders/Bonus/Bonus_Types.md | 6 +++--- lib/bonuses/BonusEnum.h | 2 +- lib/bonuses/BonusParams.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 4 ++-- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/config/gameConfig.json b/config/gameConfig.json index 5c13bb56b..4a4d3e14d 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -429,8 +429,8 @@ }, "manaPerKnowledge" : { - "type" : "MANA_PER_KNOWLEDGE", //10 mana per knowledge - "val" : 10, + "type" : "MANA_PER_KNOWLEDGE_PERCENTAGE", //1000% mana per knowledge + "val" : 1000, "valueType" : "BASE_NUMBER" }, "landMovement" : diff --git a/config/heroes/fortress.json b/config/heroes/fortress.json index e43c41ec9..d8b5b4464 100644 --- a/config/heroes/fortress.json +++ b/config/heroes/fortress.json @@ -285,7 +285,7 @@ "bonuses" : { "intelligence" : { "targetSourceType" : "SECONDARY_SKILL", - "type" : "MANA_PER_KNOWLEDGE", + "type" : "MANA_PER_KNOWLEDGE_PERCENTAGE", "updater" : "TIMES_HERO_LEVEL", "val" : 5, "valueType" : "PERCENT_TO_TARGET_TYPE" diff --git a/config/heroes/inferno.json b/config/heroes/inferno.json index 7b67505fc..60c14e984 100644 --- a/config/heroes/inferno.json +++ b/config/heroes/inferno.json @@ -129,7 +129,7 @@ "bonuses" : { "intelligence" : { "targetSourceType" : "SECONDARY_SKILL", - "type" : "MANA_PER_KNOWLEDGE", + "type" : "MANA_PER_KNOWLEDGE_PERCENTAGE", "updater" : "TIMES_HERO_LEVEL", "val" : 5, "valueType" : "PERCENT_TO_TARGET_TYPE" diff --git a/config/heroes/rampart.json b/config/heroes/rampart.json index 1001e553b..7d9a68187 100644 --- a/config/heroes/rampart.json +++ b/config/heroes/rampart.json @@ -199,7 +199,7 @@ "bonuses" : { "intelligence" : { "targetSourceType" : "SECONDARY_SKILL", - "type" : "MANA_PER_KNOWLEDGE", + "type" : "MANA_PER_KNOWLEDGE_PERCENTAGE", "updater" : "TIMES_HERO_LEVEL", "val" : 5, "valueType" : "PERCENT_TO_TARGET_TYPE" diff --git a/config/skills.json b/config/skills.json index 71974db01..fe7a01db1 100644 --- a/config/skills.json +++ b/config/skills.json @@ -708,7 +708,7 @@ "base" : { "effects" : { "main" : { - "type" : "MANA_PER_KNOWLEDGE", + "type" : "MANA_PER_KNOWLEDGE_PERCENTAGE", "valueType" : "PERCENT_TO_BASE" } } diff --git a/docs/modders/Bonus/Bonus_Types.md b/docs/modders/Bonus/Bonus_Types.md index 0df82d18e..3e2540e2f 100644 --- a/docs/modders/Bonus/Bonus_Types.md +++ b/docs/modders/Bonus/Bonus_Types.md @@ -172,11 +172,11 @@ Defines percentage of enemy troops that will be raised after battle into own arm - val: percentage of raised troops -### MANA_PER_KNOWLEDGE +### MANA_PER_KNOWLEDGE_PERCENTAGE -Defines amount of mana points that hero gains per each point of knowledge (Intelligence) +Defines percentage of mana points that hero gains per each point of knowledge (Intelligence) -- val: Amount of mana points per knowledge +- val: percentage of mana points per knowledge ### HERO_GRANTS_ATTACKS diff --git a/lib/bonuses/BonusEnum.h b/lib/bonuses/BonusEnum.h index f6cb258e0..328e286c0 100644 --- a/lib/bonuses/BonusEnum.h +++ b/lib/bonuses/BonusEnum.h @@ -162,7 +162,7 @@ class JsonNode; BONUS_NAME(BEFORE_BATTLE_REPOSITION_BLOCK) /*skill-agnostic tactics, bonus for blocking opposite tactics. For now donble side tactics is TODO.*/\ BONUS_NAME(HERO_EXPERIENCE_GAIN_PERCENT) /*skill-agnostic learning, and we can use it as a global effect also*/\ BONUS_NAME(UNDEAD_RAISE_PERCENTAGE) /*Percentage of killed enemy creatures to be raised after battle as undead*/\ - BONUS_NAME(MANA_PER_KNOWLEDGE) /*Percentage rate of translating 10 hero knowledge to mana, used to intelligence and global bonus*/\ + BONUS_NAME(MANA_PER_KNOWLEDGE_PERCENTAGE) /*Percentage rate of translating hero knowledge to 10 mana, used to intelligence and global bonus*/\ BONUS_NAME(HERO_GRANTS_ATTACKS) /*If hero can grant additional attacks to creature, value is number of attacks, subtype is creatureID*/\ BONUS_NAME(BONUS_DAMAGE_PERCENTAGE) /*If hero can grant conditional damage to creature, value is percentage, subtype is creatureID*/\ BONUS_NAME(BONUS_DAMAGE_CHANCE) /*If hero can grant additional damage to creature, value is chance, subtype is creatureID*/\ diff --git a/lib/bonuses/BonusParams.cpp b/lib/bonuses/BonusParams.cpp index 5b2765678..dc4b1b796 100644 --- a/lib/bonuses/BonusParams.cpp +++ b/lib/bonuses/BonusParams.cpp @@ -78,7 +78,7 @@ BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSu type = BonusType::SIGHT_RADIUS; else if(deprecatedSubtype == SecondarySkill::INTELLIGENCE || deprecatedSubtypeStr == "skill.intelligence") { - type = BonusType::MANA_PER_KNOWLEDGE; + type = BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE; valueType = BonusValueType::PERCENT_TO_BASE; } else if(deprecatedSubtype == SecondarySkill::SORCERY || deprecatedSubtypeStr == "skill.sorcery") diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index be24c0258..ad1e6256c 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -390,7 +390,7 @@ void CGHeroInstance::initHero(CRandomGenerator & rand) // load base hero bonuses, TODO: per-map loading of base hero bonuses // must be done separately from global bonuses since recruitable heroes in taverns // are not attached to global bonus node but need access to some global bonuses - // e.g. MANA_PER_KNOWLEDGE for correct preview and initial state after recruit for(const auto & ob : VLC->modh->heroBaseBonuses) + // e.g. MANA_PER_KNOWLEDGE_PERCENTAGE for correct preview and initial state after recruit for(const auto & ob : VLC->modh->heroBaseBonuses) // or MOVEMENT to compute initial movement before recruiting is finished const JsonNode & baseBonuses = VLC->settings()->getValue(EGameSettings::BONUSES_PER_HERO); for(const auto & b : baseBonuses.Struct()) @@ -1082,7 +1082,7 @@ std::string CGHeroInstance::nodeName() const si32 CGHeroInstance::manaLimit() const { return si32(getPrimSkillLevel(PrimarySkill::KNOWLEDGE) - * (valOfBonuses(BonusType::MANA_PER_KNOWLEDGE))); + * (valOfBonuses(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE))) / 100; } HeroTypeID CGHeroInstance::getPortraitSource() const From ff35a271766ab372a93095ee84696641d9538f0b Mon Sep 17 00:00:00 2001 From: Kris-Ja <107918765+Kris-Ja@users.noreply.github.com> Date: Sat, 23 Mar 2024 22:03:06 +0100 Subject: [PATCH 245/250] Fix loading saved games (add ESerializationVersion) --- lib/bonuses/Bonus.h | 5 +++++ lib/serializer/ESerializationVersion.h | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/bonuses/Bonus.h b/lib/bonuses/Bonus.h index 53693917e..2ed8225b5 100644 --- a/lib/bonuses/Bonus.h +++ b/lib/bonuses/Bonus.h @@ -105,6 +105,11 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this h & updater; h & propagationUpdater; h & targetSourceType; + if (h.version < Handler::Version::MANA_LIMIT && type == BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE) + { + if (valType == BonusValueType::ADDITIVE_VALUE || valType == BonusValueType::BASE_NUMBER) + val *= 100; + } } template diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 12436b1c3..2c481220e 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -37,6 +37,7 @@ enum class ESerializationVersion : int32_t DESTROYED_OBJECTS, // 834 +list of objects destroyed by player CAMPAIGN_MAP_TRANSLATIONS, // 835 +campaigns include translations for its maps JSON_FLAGS, // 836 json uses new format for flags + MANA_LIMIT, // 837 change MANA_PER_KNOWLEGDE to percentage - CURRENT = JSON_FLAGS + CURRENT = MANA_LIMIT }; From 263d439605dbc6ca27e19c690d267fe5f895d961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sun, 24 Mar 2024 07:54:11 +0100 Subject: [PATCH 246/250] Fix uninitialized variable --- lib/mapping/CMap.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index b6b520f13..6ddda8e5c 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -171,6 +171,7 @@ CMap::CMap(IGameCallback * cb) , checksum(0) , grailPos(-1, -1, -1) , grailRadius(0) + , waterMap(false) , uidCounter(0) { allHeroes.resize(VLC->heroh->size()); @@ -605,6 +606,10 @@ bool CMap::calculateWaterContent() { waterMap = true; } + else + { + waterMap = false; + } return waterMap; } From c6a9d946306e931833060d25d1d0fca2ca81344d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sun, 24 Mar 2024 17:25:30 +0100 Subject: [PATCH 247/250] Fix crash with empty zone --- lib/rmg/CZonePlacer.cpp | 62 ++++++++++++++++++++++++++------------- lib/rmg/PenroseTiling.cpp | 22 ++++++++++---- lib/rmg/PenroseTiling.h | 2 ++ 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 75f248bb3..31eccb0a2 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -906,27 +906,36 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) 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() / map.levels(), rand); - - std::map, std::set> vertexMapping; - - for (const auto & vertex : vertices) + for (int level = 0; level < levels; level++) { - distances.clear(); - for(const auto & zone : zones) + //Create different tiling for each level + // Assign zones to closest Penrose vertex + // TODO: Count zones on a level exactly? + + auto vertices = penrose.generatePenroseTiling(zones.size() / map.levels(), rand); + + std::map, std::set> vertexMapping; + + for (const auto & vertex : vertices) { - distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), 0))); + distances.clear(); + for(const auto & zone : zones) + { + if (zone.second->isUnderground() == level) + { + // FIXME: Only take into account zones on the same level as vertex + // TODO: Create separate mapping for zones on different levels + distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), level))); + } + } + auto closestZone = boost::min_element(distances, compareByDistance)->first; + + vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, level)); //Closest vertex belongs to zone } - 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 - } - - //Assign actual tiles to each zone - for (pos.z = 0; pos.z < levels; pos.z++) - { + //Assign actual tiles to each zone + pos.z = level; for (pos.x = 0; pos.x < width; pos.x++) { for (pos.y = 0; pos.y < height; pos.y++) @@ -937,27 +946,38 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) auto zone = zoneVertex.first; for (const auto & vertex : zoneVertex.second) { - if (zone->getCenter().z == pos.z) + if (zone->isUnderground() == level) distances.emplace_back(zone, metric(pos, vertex)); else distances.emplace_back(zone, std::numeric_limits::max()); } } - //Tile closes to vertex belongs to zone + //Tile closest to vertex belongs to zone auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first; closestZone->area().add(pos); map.setZoneID(pos, closestZone->getId()); } } + + for(const auto & zone : zones) + { + if(zone.second->isUnderground() == level && zone.second->area().empty()) + { + // FIXME: Some vertices are duplicated, but it's not a source of problem + logGlobal->error("Zone %d at %s is empty, dumping Penrose tiling", zone.second->getId(), zone.second->getCenter().toString()); + for (const auto & vertex : vertices) + { + logGlobal->warn("Penrose Vertex: %s", vertex.toString()); + } + throw rmgException("Empty zone after Penrose tiling"); + } + } } //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 e6ee0e9f4..72efce246 100644 --- a/lib/rmg/PenroseTiling.cpp +++ b/lib/rmg/PenroseTiling.cpp @@ -38,6 +38,16 @@ bool Point2D::operator < (const Point2D& other) const } } +std::string Point2D::toString() const +{ + //Performance is important here + std::string result = "(" + + std::to_string(this->x()) + " " + + std::to_string(this->y()) + ")"; + + return result; +} + Triangle::Triangle(bool t_123, const TIndices & inds): tiling(t_123), indices(inds) @@ -57,14 +67,14 @@ Triangle::~Triangle() Point2D Point2D::rotated(float radians) const { - float cosAngle = cos(radians); - float sinAngle = sin(radians); + float cosAngle = cos(radians); + float sinAngle = sin(radians); - // Apply rotation matrix transformation - float newX = x() * cosAngle - y() * sinAngle; - float newY = x() * sinAngle + y() * cosAngle; + // Apply rotation matrix transformation + float newX = x() * cosAngle - y() * sinAngle; + float newY = x() * sinAngle + y() * cosAngle; - return Point2D(newX, newY); + return Point2D(newX, newY); } void PenroseTiling::split(Triangle& p, std::vector& points, diff --git a/lib/rmg/PenroseTiling.h b/lib/rmg/PenroseTiling.h index bdda4c7c9..5d477a808 100644 --- a/lib/rmg/PenroseTiling.h +++ b/lib/rmg/PenroseTiling.h @@ -32,6 +32,8 @@ public: Point2D rotated(float radians) const; bool operator < (const Point2D& other) const; + + std::string toString() const; }; Point2D rotatePoint(const Point2D& point, double radians, const Point2D& origin); From 0b8dc02d2b44f87d587d00b9df1bf31adc5ce12d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sun, 24 Mar 2024 18:04:33 +0100 Subject: [PATCH 248/250] Clean up duplicated code --- lib/rmg/CZonePlacer.cpp | 30 ++++++++++++------------------ lib/rmg/RmgMap.cpp | 13 +++++++++++++ lib/rmg/RmgMap.h | 1 + 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 31eccb0a2..d62812961 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -873,6 +873,11 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) int levels = map.levels(); // Find current center of mass for each zone. Move zone to that center to balance zones sizes + std::vector zonesOnLevel; + for(int level = 0; level < levels; level++) + { + zonesOnLevel.push_back(map.getZonesOnLevel(level)); + } int3 pos; @@ -883,12 +888,9 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) for(pos.y = 0; pos.y < height; pos.y++) { distances.clear(); - for(const auto & zone : zones) + for(const auto & zone : zonesOnLevel[pos.z]) { - if (zone.second->getPos().z == pos.z) - distances.emplace_back(zone.second, static_cast(pos.dist2dSQ(zone.second->getPos()))); - else - distances.emplace_back(zone.second, std::numeric_limits::max()); + distances.emplace_back(zone.second, static_cast(pos.dist2dSQ(zone.second->getPos()))); } boost::min_element(distances, compareByDistance)->first->area().add(pos); //closest tile belongs to zone } @@ -920,14 +922,9 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) for (const auto & vertex : vertices) { distances.clear(); - for(const auto & zone : zones) + for(const auto & zone : zonesOnLevel[level]) { - if (zone.second->isUnderground() == level) - { - // FIXME: Only take into account zones on the same level as vertex - // TODO: Create separate mapping for zones on different levels - distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), level))); - } + distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), level))); } auto closestZone = boost::min_element(distances, compareByDistance)->first; @@ -946,10 +943,7 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) auto zone = zoneVertex.first; for (const auto & vertex : zoneVertex.second) { - if (zone->isUnderground() == level) - distances.emplace_back(zone, metric(pos, vertex)); - else - distances.emplace_back(zone, std::numeric_limits::max()); + distances.emplace_back(zone, metric(pos, vertex)); } } @@ -960,9 +954,9 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) } } - for(const auto & zone : zones) + for(const auto & zone : zonesOnLevel[level]) { - if(zone.second->isUnderground() == level && zone.second->area().empty()) + if(zone.second->area().empty()) { // FIXME: Some vertices are duplicated, but it's not a source of problem logGlobal->error("Zone %d at %s is empty, dumping Penrose tiling", zone.second->getId(), zone.second->getCenter().toString()); diff --git a/lib/rmg/RmgMap.cpp b/lib/rmg/RmgMap.cpp index 1e2d9ebc2..923d63da1 100644 --- a/lib/rmg/RmgMap.cpp +++ b/lib/rmg/RmgMap.cpp @@ -239,6 +239,19 @@ RmgMap::Zones & RmgMap::getZones() return zones; } +RmgMap::Zones RmgMap::getZonesOnLevel(int level) const +{ + Zones zonesOnLevel; + for(const auto& zonePair : zones) + { + if(zonePair.second->isUnderground() == level) + { + zonesOnLevel.insert(zonePair); + } + } + return zonesOnLevel; +} + bool RmgMap::isBlocked(const int3 &tile) const { assertOnMap(tile); diff --git a/lib/rmg/RmgMap.h b/lib/rmg/RmgMap.h index 26079a9c1..6caff8236 100644 --- a/lib/rmg/RmgMap.h +++ b/lib/rmg/RmgMap.h @@ -74,6 +74,7 @@ public: using ZoneVector = std::vector; Zones & getZones(); + Zones getZonesOnLevel(int level) const; void registerZone(FactionID faction); ui32 getZoneCount(FactionID faction); From 9d620b924d78e2a1164d4ee8494d9849247c5b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Tue, 26 Mar 2024 07:55:18 +0100 Subject: [PATCH 249/250] Implement TODO --- lib/rmg/CZonePlacer.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index d62812961..ae27942f2 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -912,11 +912,10 @@ void CZonePlacer::assignZones(CRandomGenerator * rand) for (int level = 0; level < levels; level++) { //Create different tiling for each level + + auto vertices = penrose.generatePenroseTiling(zonesOnLevel[level].size(), rand); + // Assign zones to closest Penrose vertex - // TODO: Count zones on a level exactly? - - auto vertices = penrose.generatePenroseTiling(zones.size() / map.levels(), rand); - std::map, std::set> vertexMapping; for (const auto & vertex : vertices) From cfc4a26f55a5a46fec42c5d5304b676177cb5250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Tue, 26 Mar 2024 08:22:57 +0100 Subject: [PATCH 250/250] Fix warning --- lib/rmg/RmgMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rmg/RmgMap.cpp b/lib/rmg/RmgMap.cpp index 923d63da1..1eaffb1e3 100644 --- a/lib/rmg/RmgMap.cpp +++ b/lib/rmg/RmgMap.cpp @@ -244,7 +244,7 @@ RmgMap::Zones RmgMap::getZonesOnLevel(int level) const Zones zonesOnLevel; for(const auto& zonePair : zones) { - if(zonePair.second->isUnderground() == level) + if(zonePair.second->isUnderground() == (bool)level) { zonesOnLevel.insert(zonePair); }