Browse Source

Merge pull request #5859 from IvanSavenko/antilag

Lag compensation for multiplayer
Ivan Savenko 2 months ago
parent
commit
b4693a9d7f
78 changed files with 1348 additions and 577 deletions
  1. 2 2
      AI/Nullkiller/AIGateway.cpp
  2. 2 2
      AI/Nullkiller/AIGateway.h
  3. 2 2
      AI/VCAI/VCAI.cpp
  4. 2 2
      AI/VCAI/VCAI.h
  5. 9 0
      client/CMakeLists.txt
  6. 3 3
      client/CPlayerInterface.cpp
  7. 2 2
      client/CPlayerInterface.h
  8. 41 15
      client/CServerHandler.cpp
  9. 9 3
      client/CServerHandler.h
  10. 4 9
      client/Client.cpp
  11. 3 0
      client/Client.h
  12. 27 1
      client/ClientCommandManager.cpp
  13. 3 0
      client/ClientCommandManager.h
  14. 0 1
      client/ClientNetPackVisitors.h
  15. 1 1
      client/HeroMovementController.cpp
  16. 0 35
      client/NetPacksClient.cpp
  17. 3 9
      client/NetPacksLobbyClient.cpp
  18. 1 1
      client/adventureMap/AdventureMapInterface.cpp
  19. 2 1
      client/adventureMap/AdventureMapInterface.h
  20. 1 1
      client/adventureMap/CMinimap.cpp
  21. 2 1
      client/adventureMap/CMinimap.h
  22. 1 1
      client/adventureMap/MapAudioPlayer.cpp
  23. 201 0
      client/netlag/NetworkLagCompensator.cpp
  24. 52 0
      client/netlag/NetworkLagCompensator.h
  25. 30 0
      client/netlag/NetworkLagPredictedPack.h
  26. 67 0
      client/netlag/NetworkLagPredictionTestVisitor.h
  27. 28 0
      client/netlag/NetworkLagReplyPrediction.h
  28. 126 0
      client/netlag/PackRollbackGeneratorVisitor.cpp
  29. 117 0
      client/netlag/PackRollbackGeneratorVisitor.h
  30. 32 31
      docs/players/Cheat_Codes.md
  31. 4 2
      lib/CMakeLists.txt
  32. 13 13
      lib/StartInfo.cpp
  33. 15 15
      lib/StartInfo.h
  34. 3 3
      lib/callback/CGameInfoCallback.cpp
  35. 3 3
      lib/callback/CGameInfoCallback.h
  36. 2 2
      lib/callback/EditorCallback.cpp
  37. 2 2
      lib/callback/EditorCallback.h
  38. 2 1
      lib/callback/IGameEventCallback.h
  39. 3 2
      lib/callback/IGameEventsReceiver.h
  40. 4 2
      lib/callback/IGameInfoCallback.h
  41. 7 7
      lib/gameState/CGameState.cpp
  42. 1 1
      lib/gameState/CGameState.h
  43. 1 14
      lib/gameState/GameStatePackVisitor.cpp
  44. 0 1
      lib/gameState/GameStatePackVisitor.h
  45. 3 3
      lib/network/NetworkConnection.cpp
  46. 1 1
      lib/network/NetworkConnection.h
  47. 18 11
      lib/network/NetworkHandler.cpp
  48. 3 2
      lib/network/NetworkHandler.h
  49. 3 0
      lib/network/NetworkInterface.h
  50. 5 5
      lib/network/NetworkServer.cpp
  51. 1 1
      lib/network/NetworkServer.h
  52. 1 1
      lib/networkPacks/NetPackVisitor.h
  53. 0 2
      lib/networkPacks/NetPacksBase.h
  54. 5 5
      lib/networkPacks/NetPacksLib.cpp
  55. 52 23
      lib/networkPacks/PacksForClient.h
  56. 5 8
      lib/networkPacks/PacksForLobby.h
  57. 3 1
      lib/pathfinder/CPathfinder.h
  58. 1 1
      lib/rewardable/Interface.cpp
  59. 17 18
      lib/serializer/GameConnection.cpp
  60. 11 9
      lib/serializer/GameConnection.h
  61. 20 0
      lib/serializer/GameConnectionID.h
  62. 21 0
      lib/serializer/PlayerConnectionID.h
  63. 1 1
      lib/serializer/RegisterTypes.h
  64. 88 139
      server/CGameHandler.cpp
  65. 19 20
      server/CGameHandler.h
  66. 1 0
      server/CMakeLists.txt
  67. 139 72
      server/CVCMIServer.cpp
  68. 27 24
      server/CVCMIServer.h
  69. 40 0
      server/IGameServer.h
  70. 8 4
      server/LobbyNetPackVisitors.h
  71. 2 14
      server/NetPacksLobbyServer.cpp
  72. 2 2
      server/ServerNetPackVisitors.h
  73. 10 11
      server/processors/PlayerMessageProcessor.cpp
  74. 3 3
      server/processors/PlayerMessageProcessor.h
  75. 1 1
      server/processors/TurnOrderProcessor.cpp
  76. 1 1
      test/game/CGameStateTest.cpp
  77. 1 1
      test/mock/mock_IGameEventCallback.h
  78. 2 2
      test/mock/mock_IGameInfoCallback.h

+ 2 - 2
AI/Nullkiller/AIGateway.cpp

@@ -269,7 +269,7 @@ void AIGateway::heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance
 	NET_EVENT_HANDLER;
 }
 
-void AIGateway::tileHidden(const std::unordered_set<int3> & pos)
+void AIGateway::tileHidden(const FowTilesType & pos)
 {
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
@@ -277,7 +277,7 @@ void AIGateway::tileHidden(const std::unordered_set<int3> & pos)
 	nullkiller->memory->removeInvisibleObjects(myCb.get());
 }
 
-void AIGateway::tileRevealed(const std::unordered_set<int3> & pos)
+void AIGateway::tileRevealed(const FowTilesType & pos)
 {
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;

+ 2 - 2
AI/Nullkiller/AIGateway.h

@@ -106,7 +106,7 @@ public:
 	void heroMoved(const TryMoveHero & details, bool verbose = true) override;
 	void heroInGarrisonChange(const CGTownInstance * town) override;
 	void centerView(int3 pos, int focusTime) override;
-	void tileHidden(const std::unordered_set<int3> & pos) override;
+	void tileHidden(const FowTilesType & pos) override;
 	void artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) override;
 	void artifactAssembled(const ArtifactLocation & al) override;
 	void showTavernWindow(const CGObjectInstance * object, const CGHeroInstance * visitor, QueryID queryID) override;
@@ -121,7 +121,7 @@ public:
 	void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override;
 	void availableArtifactsChanged(const CGBlackMarket * bm = nullptr) override;
 	void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
-	void tileRevealed(const std::unordered_set<int3> & pos) override;
+	void tileRevealed(const FowTilesType & pos) override;
 	void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
 	void heroExperienceChanged(const CGHeroInstance * hero, si64 val) override;
 	void heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val) override;

+ 2 - 2
AI/VCAI/VCAI.cpp

@@ -286,7 +286,7 @@ void VCAI::heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * to
 	//moveCreaturesToHero(town);
 }
 
-void VCAI::tileHidden(const std::unordered_set<int3> & pos)
+void VCAI::tileHidden(const FowTilesType & pos)
 {
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
@@ -295,7 +295,7 @@ void VCAI::tileHidden(const std::unordered_set<int3> & pos)
 	clearPathsInfo();
 }
 
-void VCAI::tileRevealed(const std::unordered_set<int3> & pos)
+void VCAI::tileRevealed(const FowTilesType & pos)
 {
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;

+ 2 - 2
AI/VCAI/VCAI.h

@@ -147,7 +147,7 @@ public:
 	void heroMoved(const TryMoveHero & details, bool verbose = true) override;
 	void heroInGarrisonChange(const CGTownInstance * town) override;
 	void centerView(int3 pos, int focusTime) override;
-	void tileHidden(const std::unordered_set<int3> & pos) override;
+	void tileHidden(const FowTilesType & pos) override;
 	void artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) override;
 	void artifactAssembled(const ArtifactLocation & al) override;
 	void showTavernWindow(const CGObjectInstance * object, const CGHeroInstance * visitor, QueryID queryID) override;
@@ -162,7 +162,7 @@ public:
 	void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override;
 	void availableArtifactsChanged(const CGBlackMarket * bm = nullptr) override;
 	void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
-	void tileRevealed(const std::unordered_set<int3> & pos) override;
+	void tileRevealed(const FowTilesType & pos) override;
 	void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
 	void heroExperienceChanged(const CGHeroInstance * hero, si64 val) override;
 	void heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val) override;

+ 9 - 0
client/CMakeLists.txt

@@ -88,6 +88,9 @@ set(vcmiclientcommon_SRCS
 	media/CSoundHandler.cpp
 	media/CVideoHandler.cpp
 
+	netlag/NetworkLagCompensator.cpp
+	netlag/PackRollbackGeneratorVisitor.cpp
+
 	render/AssetGenerator.cpp
 	render/CAnimation.cpp
 	render/CBitmapHandler.cpp
@@ -301,6 +304,12 @@ set(vcmiclientcommon_HEADERS
 	media/ISoundPlayer.h
 	media/IVideoPlayer.h
 
+	netlag/NetworkLagCompensator.h
+	netlag/NetworkLagPredictedPack.h
+	netlag/NetworkLagPredictionTestVisitor.h
+	netlag/NetworkLagReplyPrediction.h
+	netlag/PackRollbackGeneratorVisitor.h
+
 	render/AssetGenerator.h
 	render/CAnimation.h
 	render/CBitmapHandler.h

+ 3 - 3
client/CPlayerInterface.cpp

@@ -1184,14 +1184,14 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
 	ENGINE->windows().pushWindow(wnd);
 }
 
-void CPlayerInterface::tileRevealed(const std::unordered_set<int3> &pos)
+void CPlayerInterface::tileRevealed(const FowTilesType &pos)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	//FIXME: wait for dialog? Magi hut/eye would benefit from this but may break other areas
 	adventureInt->onMapTilesChanged(pos);
 }
 
-void CPlayerInterface::tileHidden(const std::unordered_set<int3> &pos)
+void CPlayerInterface::tileHidden(const FowTilesType &pos)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	adventureInt->onMapTilesChanged(pos);
@@ -1341,7 +1341,7 @@ void CPlayerInterface::objectPropertyChanged(const SetObjectProperty * sop)
 
 		//redraw minimap if owner changed
 		std::set<int3> pos = obj->getBlockedPos();
-		std::unordered_set<int3> upos(pos.begin(), pos.end());
+		FowTilesType upos(pos.begin(), pos.end());
 		adventureInt->onMapTilesChanged(upos);
 
 		assert(cb->getTownsInfo().size() == localState->getOwnedTowns().size());

+ 2 - 2
client/CPlayerInterface.h

@@ -126,8 +126,8 @@ protected: // Call-ins from server, should not be called directly, but only via
 	void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID) override;
 	void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor) override;
 	void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override; //called when a hero casts a spell
-	void tileHidden(const std::unordered_set<int3> &pos) override; //called when given tiles become hidden under fog of war
-	void tileRevealed(const std::unordered_set<int3> &pos) override; //called when fog of war disappears from given tiles
+	void tileHidden(const FowTilesType &pos) override; //called when given tiles become hidden under fog of war
+	void tileRevealed(const FowTilesType &pos) override; //called when fog of war disappears from given tiles
 	void newObject(const CGObjectInstance * obj) override;
 	void availableArtifactsChanged(const CGBlackMarket *bm = nullptr) override; //bm may be nullptr, then artifacts are changed in the global pool (used by merchants in towns)
 	void yourTurn(QueryID queryID) override;

+ 41 - 15
client/CServerHandler.cpp

@@ -8,22 +8,26 @@
  *
  */
 #include "StdInc.h"
-
 #include "CServerHandler.h"
+
+#include "CPlayerInterface.h"
 #include "Client.h"
-#include "ServerRunner.h"
 #include "GameChatHandler.h"
-#include "CPlayerInterface.h"
 #include "GameEngine.h"
 #include "GameInstance.h"
-#include "gui/WindowHandler.h"
+#include "LobbyClientNetPackVisitors.h"
+#include "ServerRunner.h"
 
 #include "globalLobby/GlobalLobbyClient.h"
+
+#include "gui/WindowHandler.h"
+
 #include "lobby/CSelectionBase.h"
 #include "lobby/CLobbyScreen.h"
 #include "lobby/CBonusSelection.h"
-#include "windows/InfoWindows.h"
-#include "windows/GUIClasses.h"
+
+#include "netlag/NetworkLagCompensator.h"
+
 #include "media/CMusicHandler.h"
 #include "media/IVideoPlayer.h"
 
@@ -31,6 +35,9 @@
 #include "mainmenu/CPrologEpilogVideo.h"
 #include "mainmenu/CHighScoreScreen.h"
 
+#include "windows/InfoWindows.h"
+#include "windows/GUIClasses.h"
+
 #include "../lib/CConfigHandler.h"
 #include "../lib/GameLibrary.h"
 #include "../lib/texts/CGeneralTextHandler.h"
@@ -48,14 +55,12 @@
 #include "../lib/mapObjects/MiscObjects.h"
 #include "../lib/modding/ModIncompatibility.h"
 #include "../lib/rmg/CMapGenOptions.h"
-#include "../lib/serializer/Connection.h"
-#include "../lib/filesystem/Filesystem.h"
+#include "../lib/serializer/GameConnection.h"
 #include "../lib/UnlockGuard.h"
 
 #include <boost/uuid/uuid.hpp>
 #include <boost/uuid/uuid_io.hpp>
 #include <boost/uuid/uuid_generators.hpp>
-#include "LobbyClientNetPackVisitors.h"
 
 #include <vcmi/events/EventBus.h>
 #include <SDL_thread.h>
@@ -128,7 +133,7 @@ void CServerHandler::threadRunNetwork()
 
 void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode newServerMode, const std::vector<std::string> & playerNames)
 {
-	hostClientId = -1;
+	hostClientId = GameConnectionID::INVALID;
 	setState(EClientState::NONE);
 	serverMode = newServerMode;
 	mapToStart = nullptr;
@@ -269,7 +274,7 @@ void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netCon
 		getGlobalLobby().sendProxyConnectionLogin(netConnection);
 	}
 
-	logicConnection = std::make_shared<CConnection>(netConnection);
+	logicConnection = std::make_shared<GameConnection>(netConnection);
 	logicConnection->uuid = uuid;
 	logicConnection->enterLobbyConnectionMode();
 	sendClientConnecting();
@@ -297,7 +302,7 @@ bool CServerHandler::isMyColor(PlayerColor color) const
 	return isClientColor(logicConnection->connectionID, color);
 }
 
-ui8 CServerHandler::myFirstId() const
+PlayerConnectionID CServerHandler::myFirstId() const
 {
 	return clientFirstId(logicConnection->connectionID);
 }
@@ -508,7 +513,7 @@ void CServerHandler::sendMessage(const std::string & txt) const
 		if(id.length())
 		{
 			LobbyChangeHost lch;
-			lch.newHostConnectionId = boost::lexical_cast<int>(id);
+			lch.newHostConnectionId = static_cast<GameConnectionID>(boost::lexical_cast<int>(id));
 			sendLobbyPack(lch);
 		}
 	}
@@ -520,7 +525,7 @@ void CServerHandler::sendMessage(const std::string & txt) const
 		readed >> playerColorId;
 		if(connectedId.length() && playerColorId.length())
 		{
-			ui8 connected = boost::lexical_cast<int>(connectedId);
+			auto connected = static_cast<PlayerConnectionID>(boost::lexical_cast<int>(connectedId));
 			auto color = PlayerColor(boost::lexical_cast<int>(playerColorId));
 			if(color.isValidPlayer() && playerNames.find(connected) != playerNames.end())
 			{
@@ -612,11 +617,22 @@ void CServerHandler::startMapAfterConnection(std::shared_ptr<CMapInfo> to)
 	mapToStart = to;
 }
 
+void CServerHandler::enableLagCompensation(bool on)
+{
+	if (on)
+		networkLagCompensator = std::make_unique<NetworkLagCompensator>(getNetworkHandler(),  client->gameStatePtr());
+	else
+		networkLagCompensator.reset();
+}
+
 void CServerHandler::startGameplay(std::shared_ptr<CGameState> gameState)
 {
 	if(GAME->mainmenu())
 		GAME->mainmenu()->disable();
 
+	if (isGuest())
+		networkLagCompensator = std::make_unique<NetworkLagCompensator>(getNetworkHandler(), gameState);
+
 	switch(si->mode)
 	{
 	case EStartMode::NEW_GAME:
@@ -941,10 +957,12 @@ void CServerHandler::visitForLobby(CPackForLobby & lobbyPack)
 
 void CServerHandler::visitForClient(CPackForClient & clientPack)
 {
+	if (networkLagCompensator && networkLagCompensator->verifyReply(clientPack))
+		return;
+
 	client->handlePack(clientPack);
 }
 
-
 void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const
 {
 	if(getState() != EClientState::STARTING)
@@ -960,3 +978,11 @@ bool CServerHandler::inGame() const
 {
 	return logicConnection != nullptr;
 }
+
+void CServerHandler::sendGamePack(const CPackForServer & pack) const
+{
+	if (networkLagCompensator)
+		networkLagCompensator->tryPredictReply(pack);
+
+	logicConnection->sendPack(pack);
+}

+ 9 - 3
client/CServerHandler.h

@@ -17,7 +17,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CConnection;
+class GameConnection;
 class PlayerColor;
 struct StartInfo;
 struct TurnTimerInfo;
@@ -26,12 +26,14 @@ class CMapInfo;
 class CGameState;
 struct ClientPlayer;
 struct CPackForLobby;
+struct CPackForServer;
 struct CPackForClient;
 
 class HighScoreParameter;
 
 VCMI_LIB_NAMESPACE_END
 
+class NetworkLagCompensator;
 class CClient;
 class CBaseForLobbyApply;
 class GlobalLobbyClient;
@@ -100,6 +102,7 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor
 	std::unique_ptr<GlobalLobbyClient> lobbyClient;
 	std::unique_ptr<GameChatHandler> gameChat;
 	std::unique_ptr<IServerRunner> serverRunner;
+	std::unique_ptr<NetworkLagCompensator> networkLagCompensator;
 	std::shared_ptr<CMapInfo> mapToStart;
 	std::vector<std::string> localPlayerNames;
 
@@ -125,7 +128,7 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor
 
 public:
 	/// High-level connection overlay that is capable of (de)serializing network data
-	std::shared_ptr<CConnection> logicConnection;
+	std::shared_ptr<GameConnection> logicConnection;
 
 	////////////////////
 	// FIXME: Bunch of crutches to glue it all together
@@ -147,6 +150,7 @@ public:
 	void resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode serverMode, const std::vector<std::string> & playerNames);
 	void startLocalServerAndConnect(bool connectToLobby);
 	void connectToServer(const std::string & addr, const ui16 port);
+	void enableLagCompensation(bool on);
 
 	GameChatHandler & getGameChat();
 	GlobalLobbyClient & getGlobalLobby();
@@ -156,7 +160,7 @@ public:
 	std::set<PlayerColor> getHumanColors();
 	PlayerColor myFirstColor() const;
 	bool isMyColor(PlayerColor color) const;
-	ui8 myFirstId() const; // Used by chat only!
+	PlayerConnectionID myFirstId() const; // Used by chat only!
 
 	EClientState getState() const;
 	void setState(EClientState newState);
@@ -214,4 +218,6 @@ public:
 
 	void visitForLobby(CPackForLobby & lobbyPack);
 	void visitForClient(CPackForClient & clientPack);
+
+	void sendGamePack(const CPackForServer & pack) const;
 };

+ 4 - 9
client/Client.cpp

@@ -30,16 +30,16 @@
 #include "../lib/callback/CDynLibHandler.h"
 #include "../lib/callback/CGlobalAI.h"
 #include "../lib/callback/IGameInfoCallback.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "../lib/gameState/CGameState.h"
 #include "../lib/CPlayerState.h"
 #include "../lib/CThreadHelper.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/UnlockGuard.h"
-#include "../lib/serializer/Connection.h"
 #include "../lib/mapObjects/army/CArmedInstance.h"
 #include "../lib/mapping/CMapService.h"
 #include "../lib/pathfinder/CGPathNode.h"
-#include "../lib/filesystem/Filesystem.h"
+#include "../lib/serializer/GameConnection.h"
 
 #include <memory>
 #include <vcmi/events/EventBus.h>
@@ -125,7 +125,6 @@ events::EventBus * CClient::eventBus() const
 void CClient::newGame(std::shared_ptr<CGameState> initializedGameState)
 {
 	GAME->server().th->update();
-	CMapService mapService;
 	assert(initializedGameState);
 	gamestate = initializedGameState;
 	gamestate->preInit(LIBRARY);
@@ -144,13 +143,11 @@ void CClient::loadGame(std::shared_ptr<CGameState> initializedGameState)
 	logNetwork->info("Game state was transferred over network, loading.");
 	gamestate = initializedGameState;
 	gamestate->preInit(LIBRARY);
-	gamestate->updateOnLoad(GAME->server().si.get());
+	gamestate->updateOnLoad(*GAME->server().si);
 	logNetwork->info("Game loaded, initialize interfaces.");
 
 	initMapHandler();
-
 	reinitScripting();
-
 	initPlayerEnvironments();
 	initPlayerInterfaces();
 }
@@ -363,15 +360,13 @@ std::optional<BattleAction> CClient::makeSurrenderRetreatDecision(PlayerColor pl
 
 int CClient::sendRequest(const CPackForServer & request, PlayerColor player, bool waitTillRealize)
 {
-	static ui32 requestCounter = 1;
-
 	ui32 requestID = requestCounter++;
 	logNetwork->trace("Sending a request \"%s\". It'll have an ID=%d.", typeid(request).name(), requestID);
 
 	waitingRequest.pushBack(requestID);
 	request.requestID = requestID;
 	request.player = player;
-	GAME->server().logicConnection->sendPack(request);
+	GAME->server().sendGamePack(request);
 	if(vstd::contains(playerint, player))
 		playerint[player]->requestSent(&request, requestID);
 

+ 3 - 0
client/Client.h

@@ -125,6 +125,8 @@ public:
 class CClient : public Environment, public IClient
 {
 	std::shared_ptr<CGameState> gamestate;
+	int requestCounter = 1;
+
 public:
 	std::map<PlayerColor, std::shared_ptr<CGameInterface>> playerint;
 	std::map<PlayerColor, std::shared_ptr<CBattleGameInterface>> battleints;
@@ -142,6 +144,7 @@ public:
 	vstd::CLoggerBase * logger() const override;
 	events::EventBus * eventBus() const override;
 
+	std::shared_ptr<CGameState> gameStatePtr() { return gamestate; }
 	CGameState & gameState() { return *gamestate; }
 	const CGameState & gameState() const { return *gamestate; }
 	IGameInfoCallback & gameInfo();

+ 27 - 1
client/ClientCommandManager.cpp

@@ -38,9 +38,9 @@
 #include "../lib/modding/CModHandler.h"
 #include "../lib/modding/ContentTypeHandler.h"
 #include "../lib/modding/ModUtility.h"
+#include "../lib/serializer/GameConnection.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/logging/VisualLogger.h"
-#include "../lib/serializer/Connection.h"
 
 #ifdef SCRIPTING_ENABLED
 #include "../lib/ScriptHandler.h"
@@ -339,6 +339,29 @@ void ClientCommandManager::handleGetConfigCommand()
 	printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n");
 }
 
+void ClientCommandManager::handleAntilagCommand(std::istringstream& singleWordBuffer)
+{
+	std::string commandName;
+	singleWordBuffer >> commandName;
+
+	if (commandName == "on")
+	{
+		GAME->server().enableLagCompensation(true);
+		printCommandMessage("Network lag compensation is now enabled.\n");
+	}
+	else if (commandName == "off")
+	{
+		GAME->server().enableLagCompensation(true);
+		printCommandMessage("Network lag compensation is now disabled.\n");
+	}
+	else
+	{
+		printCommandMessage("Unexpected syntax. Supported forms:\n");
+		printCommandMessage("'antilag on'\n");
+		printCommandMessage("'antilag off'\n");
+	}
+}
+
 void ClientCommandManager::handleGetScriptsCommand()
 {
 #if SCRIPTING_ENABLED
@@ -596,6 +619,9 @@ void ClientCommandManager::processCommand(const std::string & message, bool call
 	else if(commandName == "setBattleAI")
 		handleSetBattleAICommand(singleWordBuffer);
 
+	else if(commandName == "antilag")
+		handleAntilagCommand(singleWordBuffer);
+
 	else if(commandName == "redraw")
 		handleRedrawCommand();
 

+ 3 - 0
client/ClientCommandManager.h

@@ -45,6 +45,9 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a
 	// Redraw the current screen
 	void handleRedrawCommand();
 
+	// Enable or disable network lag compensation
+	void handleAntilagCommand(std::istringstream& singleWordBuffer);
+
 	// Extracts all translateable game texts into Translation directory, separating files on per-mod basis
 	void handleTranslateGameCommand(bool onlyMissing);
 

+ 0 - 1
client/ClientNetPackVisitors.h

@@ -58,7 +58,6 @@ public:
 	void visitChangeObjPos(ChangeObjPos & pack) override;
 	void visitPlayerEndsTurn(PlayerEndsTurn & pack) override;
 	void visitPlayerEndsGame(PlayerEndsGame & pack) override;
-	void visitPlayerReinitInterface(PlayerReinitInterface & pack) override;
 	void visitRemoveBonus(RemoveBonus & pack) override;
 	void visitRemoveObject(RemoveObject & pack) override;
 	void visitTryMoveHero(TryMoveHero & pack) override;

+ 1 - 1
client/HeroMovementController.cpp

@@ -167,7 +167,7 @@ void HeroMovementController::onTryMoveHero(const CGHeroInstance * hero, const Tr
 		GAME->interface()->localState->hasPath(hero) &&
 		GAME->interface()->localState->getPath(hero).lastNode().coord == details.attackedFrom;
 
-	std::unordered_set<int3> changedTiles {
+	FowTilesType changedTiles {
 		hero->convertToVisitablePos(details.start),
 		hero->convertToVisitablePos(details.end)
 	};

+ 0 - 35
client/NetPacksClient.cpp

@@ -31,7 +31,6 @@
 #include "../lib/callback/CCallback.h"
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/FileInfo.h"
-#include "../lib/serializer/Connection.h"
 #include "../lib/texts/CGeneralTextHandler.h"
 #include "../lib/GameLibrary.h"
 #include "../lib/mapping/CMap.h"
@@ -422,40 +421,6 @@ void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
 	}
 }
 
-void ApplyClientNetPackVisitor::visitPlayerReinitInterface(PlayerReinitInterface & pack)
-{
-	auto initInterfaces = [this]()
-	{
-		cl.initPlayerInterfaces();
-
-		for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player)
-		{
-			if(cl.gameState().isPlayerMakingTurn(player))
-			{
-				callAllInterfaces(cl, &IGameEventsReceiver::playerStartsTurn, player);
-				callOnlyThatInterface(cl, player, &CGameInterface::yourTurn, QueryID::NONE);
-			}
-		}
-	};
-	
-	for(auto player : pack.players)
-	{
-		auto & plSettings = GAME->server().si->getIthPlayersSettings(player);
-		if(pack.playerConnectionId == PlayerSettings::PLAYER_AI)
-		{
-			plSettings.connectedPlayerIDs.clear();
-			cl.initPlayerEnvironments();
-			initInterfaces();
-		}
-		else if(pack.playerConnectionId == GAME->server().logicConnection->connectionID)
-		{
-			plSettings.connectedPlayerIDs.insert(pack.playerConnectionId);
-			cl.playerint.clear();
-			initInterfaces();
-		}
-	}
-}
-
 void ApplyClientNetPackVisitor::visitRemoveBonus(RemoveBonus & pack)
 {
 	switch(pack.who)

+ 3 - 9
client/NetPacksLobbyClient.cpp

@@ -36,9 +36,9 @@
 #include "windows/GUIClasses.h"
 
 #include "../lib/CConfigHandler.h"
-#include "../lib/texts/CGeneralTextHandler.h"
-#include "../lib/serializer/Connection.h"
 #include "../lib/campaign/CampaignState.h"
+#include "../lib/serializer/GameConnection.h"
+#include "../lib/texts/CGeneralTextHandler.h"
 
 void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack)
 {
@@ -152,14 +152,8 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyPrepareStartGame(LobbyPrepareS
 
 void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
 {
-	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.logicConnection->connectionID)
+	if(handler.si->mode != EStartMode::LOAD_GAME)
 	{
 		auto modeBackup = handler.si->mode;
 		handler.si = pack.initializedStartInfo;

+ 1 - 1
client/adventureMap/AdventureMapInterface.cpp

@@ -351,7 +351,7 @@ void AdventureMapInterface::onHeroOrderChanged()
 	widget->getHeroList()->updateWidget();
 }
 
-void AdventureMapInterface::onMapTilesChanged(boost::optional<std::unordered_set<int3>> positions)
+void AdventureMapInterface::onMapTilesChanged(boost::optional<FowTilesType> positions)
 {
 	if (positions)
 		widget->getMinimap()->updateTiles(*positions);

+ 2 - 1
client/adventureMap/AdventureMapInterface.h

@@ -22,6 +22,7 @@ struct CGPathNode;
 struct ObjectPosInfo;
 struct Component;
 class int3;
+using FowTilesType = std::set<int3>;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -131,7 +132,7 @@ public:
 	void onCurrentPlayerChanged(PlayerColor playerID);
 
 	/// Called by PlayerInterface when specific map tile changed and must be updated on minimap
-	void onMapTilesChanged(boost::optional<std::unordered_set<int3>> positions);
+	void onMapTilesChanged(boost::optional<FowTilesType> positions);
 
 	/// Called by PlayerInterface when hero starts movement
 	void onHeroMovementStarted(const CGHeroInstance * hero);

+ 1 - 1
client/adventureMap/CMinimap.cpp

@@ -276,7 +276,7 @@ void CMinimap::updateVisibleHeroes()
 	}
 }
 
-void CMinimap::updateTiles(const std::unordered_set<int3> & positions)
+void CMinimap::updateTiles(const FowTilesType & positions)
 {
 	if(minimap)
 	{

+ 2 - 1
client/adventureMap/CMinimap.h

@@ -13,6 +13,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 class ColorRGBA;
+using FowTilesType = std::set<int3>;
 VCMI_LIB_NAMESPACE_END
 
 class Canvas;
@@ -73,6 +74,6 @@ public:
 
 	void showAll(Canvas & to) override;
 
-	void updateTiles(const std::unordered_set<int3> & positions);
+	void updateTiles(const FowTilesType & positions);
 };
 

+ 1 - 1
client/adventureMap/MapAudioPlayer.cpp

@@ -163,7 +163,7 @@ void MapAudioPlayer::updateAmbientSounds()
 	};
 
 	int3 pos = currentSelection->getSightCenter();
-	std::unordered_set<int3> tiles;
+	FowTilesType tiles;
 	GAME->interface()->cb->getVisibleTilesInRange(tiles, pos, ENGINE->sound().ambientGetRange(), int3::DIST_CHEBYSHEV);
 	for(int3 tile : tiles)
 	{

+ 201 - 0
client/netlag/NetworkLagCompensator.cpp

@@ -0,0 +1,201 @@
+/*
+ * NetworkLagCompensator.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 "NetworkLagCompensator.h"
+
+#include "NetworkLagPredictionTestVisitor.h"
+#include "NetworkLagReplyPrediction.h"
+#include "PackRollbackGeneratorVisitor.h"
+
+#include "../CServerHandler.h"
+#include "../Client.h"
+#include "../GameEngine.h"
+#include "../GameInstance.h"
+
+#include "../../lib/serializer/BinarySerializer.h"
+#include "../../lib/serializer/GameConnection.h"
+#include "../../server/CGameHandler.h"
+
+class RollbackNotSupportedException : public std::runtime_error
+{
+public:
+	using std::runtime_error::runtime_error;
+};
+
+NetworkLagCompensator::NetworkLagCompensator(INetworkHandler & network, const std::shared_ptr<CGameState> & gs)
+	: gameState(gs)
+{
+	antilagNetConnection = network.createAsyncConnection(*this);
+	antilagGameConnection = std::make_shared<GameConnection>(antilagNetConnection);
+}
+
+NetworkLagCompensator::~NetworkLagCompensator() = default;
+
+void NetworkLagCompensator::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage)
+{
+	// should never be called
+	throw std::runtime_error("AntilagServer::onDisconnected called!");
+}
+
+void NetworkLagCompensator::onPacketReceived(const std::shared_ptr<INetworkConnection> & connection, const std::vector<std::byte> & message)
+{
+	std::scoped_lock interfaceLock(ENGINE->interfaceMutex);
+
+	auto basePack = antilagGameConnection->retrievePack(message);
+	auto * serverPack = dynamic_cast<CPackForServer *>(basePack.get());
+
+	NetworkLagPredictionTestVisitor packVisitor;
+	serverPack->visit(packVisitor);
+	if(!packVisitor.canBeApplied())
+		return;
+
+	logGlobal->trace("Predicting effects of pack '%s'", typeid(*serverPack).name());
+
+	NetworkLagReplyPrediction newPrediction;
+	newPrediction.requestID = serverPack->requestID;
+	newPrediction.senderID = serverPack->player;
+	predictedReplies.push_back(std::move(newPrediction));
+
+	try
+	{
+		CGameHandler gameHandler(*this, gameState);
+		gameHandler.handleReceivedPack(GameConnectionID::FIRST_CONNECTION, *serverPack);
+	}
+	catch(const RollbackNotSupportedException &)
+	{
+		return;
+	}
+}
+
+void NetworkLagCompensator::tryPredictReply(const CPackForServer & request)
+{
+	antilagGameConnection->sendPack(request);
+	logGlobal->trace("Scheduled prediction of effects of pack '%s'", typeid(request).name());
+}
+
+bool NetworkLagCompensator::verifyReply(const CPackForClient & pack)
+{
+	logGlobal->trace("Verifying reply: received pack '%s'", typeid(pack).name());
+
+	const auto * packageReceived = dynamic_cast<const PackageReceived *>(&pack);
+	const auto * packageApplied = dynamic_cast<const PackageApplied *>(&pack);
+
+	if(packageReceived)
+	{
+		assert(currentPackageID == invalidPackageID);
+
+		if(!predictedReplies.empty() && predictedReplies.front().requestID == packageReceived->requestID)
+		{
+			[[maybe_unused]] const auto & nextPrediction = predictedReplies.front();
+			assert(nextPrediction.senderID == packageReceived->player);
+			assert(nextPrediction.requestID == packageReceived->requestID);
+			currentPackageID = packageReceived->requestID;
+		}
+	}
+
+	if(currentPackageID == invalidPackageID)
+	{
+		// this is system package or reply to actions of another player
+		// TODO: consider reapplying all our predictions, in case if this event invalidated our prediction
+		return false;
+	}
+
+	NetworkLagPredictedPack packWriter;
+	BinarySerializer serializer(&packWriter);
+	serializer & &pack;
+
+	if(packWriter.predictedPackData == predictedReplies.front().writtenPacks.front().predictedPackData)
+	{
+		// Our prediction was sucessful - drop rollback information for this pack
+		predictedReplies.front().writtenPacks.erase(predictedReplies.front().writtenPacks.begin());
+
+		if(predictedReplies.front().writtenPacks.empty())
+		{
+			predictedReplies.erase(predictedReplies.begin());
+			currentPackageID = invalidPackageID;
+			return true;
+		}
+	}
+	else
+	{
+		// Prediction was incorrect - rollback everything that is left in this prediction and use real server packs
+		for(auto & prediction : boost::adaptors::reverse(predictedReplies.front().writtenPacks))
+		{
+			for(auto & pack : prediction.rollbackPacks)
+				GAME->server().client->handlePack(*pack);
+		}
+		predictedReplies.erase(predictedReplies.begin());
+		currentPackageID = invalidPackageID;
+		return false;
+	}
+
+	if(packageApplied)
+	{
+		assert(currentPackageID == packageApplied->requestID);
+		assert(!predictedReplies.empty());
+		assert(currentPackageID == predictedReplies.front().requestID);
+		assert(predictedReplies.front().writtenPacks.empty());
+		predictedReplies.erase(predictedReplies.begin());
+		currentPackageID = invalidPackageID;
+	}
+
+	return true;
+}
+
+void NetworkLagCompensator::setState(EServerState value)
+{
+	// no-op
+}
+
+EServerState NetworkLagCompensator::getState() const
+{
+	return EServerState::GAMEPLAY;
+}
+
+bool NetworkLagCompensator::isPlayerHost(const PlayerColor & color) const
+{
+	return false; // TODO?
+}
+
+bool NetworkLagCompensator::hasPlayerAt(PlayerColor player, GameConnectionID c) const
+{
+	return true; // TODO?
+}
+
+bool NetworkLagCompensator::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const
+{
+	return false; // TODO?
+}
+
+void NetworkLagCompensator::applyPack(CPackForClient & pack)
+{
+	PackRollbackGeneratorVisitor visitor(*gameState);
+	pack.visit(visitor);
+	if(!visitor.canBeRolledBack())
+	{
+		logGlobal->trace("Prediction not possible: pack '%s'", typeid(pack).name());
+		throw RollbackNotSupportedException(std::string("Prediction not possible ") + typeid(pack).name());
+	}
+
+	logGlobal->trace("Prediction: pack '%s'", typeid(pack).name());
+
+	NetworkLagPredictedPack packWriter;
+	BinarySerializer serializer(&packWriter);
+	serializer & &pack;
+	packWriter.rollbackPacks = visitor.acquireRollbackPacks();
+	predictedReplies.back().writtenPacks.push_back(std::move(packWriter));
+
+	GAME->server().client->handlePack(pack);
+}
+
+void NetworkLagCompensator::sendPack(CPackForClient & pack, GameConnectionID connectionID)
+{
+	applyPack(pack);
+}

+ 52 - 0
client/netlag/NetworkLagCompensator.h

@@ -0,0 +1,52 @@
+/*
+ * NetworkLagCompensator.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"
+#include "../../server/IGameServer.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+struct CPackForServer;
+class CGameState;
+class GameConnection;
+VCMI_LIB_NAMESPACE_END
+
+class NetworkLagReplyPrediction;
+
+/// Fake server that is used by client to make a quick prediction on what real server would reply without waiting for network latency
+class NetworkLagCompensator final : public IGameServer, public INetworkConnectionListener, boost::noncopyable
+{
+	std::vector<NetworkLagReplyPrediction> predictedReplies;
+	std::shared_ptr<INetworkConnection> antilagNetConnection;
+	std::shared_ptr<GameConnection> antilagGameConnection;
+	std::shared_ptr<CGameState> gameState;
+
+	static constexpr uint32_t invalidPackageID = std::numeric_limits<uint32_t>::max();
+	uint32_t currentPackageID = invalidPackageID;
+
+	// IGameServer impl
+	void setState(EServerState value) override;
+	EServerState getState() const override;
+	bool isPlayerHost(const PlayerColor & color) const override;
+	bool hasPlayerAt(PlayerColor player, GameConnectionID connection) const override;
+	bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const override;
+	void applyPack(CPackForClient & pack) override;
+	void sendPack(CPackForClient & pack, GameConnectionID connectionID) override;
+
+	void onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage) override;
+	void onPacketReceived(const std::shared_ptr<INetworkConnection> & connection, const std::vector<std::byte> & message) override;
+
+public:
+	NetworkLagCompensator(INetworkHandler & network, const std::shared_ptr<CGameState> & gs);
+	~NetworkLagCompensator();
+
+	void tryPredictReply(const CPackForServer & request);
+	bool verifyReply(const CPackForClient & reply);
+};

+ 30 - 0
client/netlag/NetworkLagPredictedPack.h

@@ -0,0 +1,30 @@
+/*
+ * NetworkLagPredictedPack.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/networkPacks/NetPacksBase.h"
+#include "../../lib/serializer/CSerializer.h"
+
+/// Class that contains data for a single pack that client expects to receive from server
+class NetworkLagPredictedPack final : public IBinaryWriter
+{
+public:
+	/// Serialized data of predicted pack, for comparison with version from server
+	std::vector<std::byte> predictedPackData;
+
+	/// List of packs that must be applied on client in case of failed prediction
+	std::vector<std::unique_ptr<CPackForClient>> rollbackPacks;
+
+	int write(const std::byte * data, unsigned size) final
+	{
+		predictedPackData.insert(predictedPackData.end(), data, data + size);
+		return size;
+	}
+};

+ 67 - 0
client/netlag/NetworkLagPredictionTestVisitor.h

@@ -0,0 +1,67 @@
+/*
+ * AntilagServer.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/networkPacks/NetPackVisitor.h"
+
+/// Class that filters which client request to server can be predicted, at least partially
+class NetworkLagPredictionTestVisitor final : public ICPackVisitor
+{
+	bool canBeAppliedValue = false;
+
+	//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
+	{
+		canBeAppliedValue = true;
+	}
+	//void visitCastleTeleportHero(CastleTeleportHero & pack) override;
+	void visitArrangeStacks(ArrangeStacks & pack) override
+	{
+		canBeAppliedValue = true;
+	}
+	//void visitBulkMoveArmy(BulkMoveArmy & pack) override;
+	//void visitBulkSplitStack(BulkSplitStack & pack) override;
+	//void visitBulkMergeStacks(BulkMergeStacks & pack) override;
+	//void visitBulkSplitAndRebalanceStack(BulkSplitAndRebalanceStack & pack) override;
+	//void visitDisbandCreature(DisbandCreature & pack) override;
+	//void visitBuildStructure(BuildStructure & pack) override;
+	//void visitSpellResearch(SpellResearch & pack) override;
+	//void visitVisitTownBuilding(VisitTownBuilding & 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 visitManageBackpackArtifacts(ManageBackpackArtifacts & pack) override;
+	//void visitManageEquippedArtifacts(ManageEquippedArtifacts & 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;
+	//void visitSaveLocalState(SaveLocalState & pack) override;
+
+public:
+	/// Returns true if game should try to predict results of pack on which this class was applied
+	bool canBeApplied() const
+	{
+		return canBeAppliedValue;
+	}
+};

+ 28 - 0
client/netlag/NetworkLagReplyPrediction.h

@@ -0,0 +1,28 @@
+/*
+ * NetworkLagReplyPrediction.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 "NetworkLagPredictedPack.h"
+
+#include "../../lib/constants/EntityIdentifiers.h"
+
+/// Class that contains data for a single pack that client sent to server
+class NetworkLagReplyPrediction
+{
+public:
+	/// Sender that sent this reply. Generally - this should be only local client color
+	PlayerColor senderID;
+
+	/// Unique request ID that was written into sent pack
+	uint32_t requestID;
+
+	/// One or more packs that client expects to receive from server as a reply
+	std::vector<NetworkLagPredictedPack> writtenPacks;
+};

+ 126 - 0
client/netlag/PackRollbackGeneratorVisitor.cpp

@@ -0,0 +1,126 @@
+/*
+ * PackRollbackGeneratorVisitor.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 "PackRollbackGeneratorVisitor.h"
+
+#include "../lib/gameState/CGameState.h"
+#include "../lib/mapObjects/CGHeroInstance.h"
+
+void PackRollbackGeneratorVisitor::visitPackageReceived(PackageReceived & pack)
+{
+	success = true;
+	// no-op rollback?
+}
+
+void PackRollbackGeneratorVisitor::visitPackageApplied(PackageApplied & pack)
+{
+	success = true;
+	// no-op rollback?
+}
+
+void PackRollbackGeneratorVisitor::visitPlayerBlocked(PlayerBlocked & pack)
+{
+	success = true;
+	// no-op rollback?
+}
+
+void PackRollbackGeneratorVisitor::visitSwapStacks(SwapStacks & pack)
+{
+	auto rollbackSwap = std::make_unique<SwapStacks>();
+
+	rollbackSwap->srcArmy = pack.dstArmy;
+	rollbackSwap->dstArmy = pack.srcArmy;
+	rollbackSwap->srcSlot = pack.dstSlot;
+	rollbackSwap->dstSlot = pack.srcSlot;
+
+	rollbackPacks.push_back(std::move(rollbackSwap));
+	success = true;
+}
+
+void PackRollbackGeneratorVisitor::visitRebalanceStacks(RebalanceStacks & pack)
+{
+	const auto * srcObject = gs.getObjInstance(pack.srcArmy);
+	const auto * dstObject = gs.getObjInstance(pack.dstArmy);
+
+	const auto * srcArmy = dynamic_cast<const CArmedInstance *>(srcObject);
+	const auto * dstArmy = dynamic_cast<const CArmedInstance *>(dstObject);
+
+	if (srcArmy->getStack(pack.srcSlot).getTotalExperience() != 0 ||
+	   dstArmy->getStack(pack.srcSlot).getTotalExperience() != 0 ||
+	   srcArmy->getStack(pack.srcSlot).getSlot(ArtifactPosition::CREATURE_SLOT)->artifactID.hasValue())
+	{
+		// TODO: rollback creature artifacts & stack experience
+		return;
+	}
+
+	auto rollbackRebalance = std::make_unique<RebalanceStacks>();
+	rollbackRebalance->srcArmy = pack.dstArmy;
+	rollbackRebalance->dstArmy = pack.srcArmy;
+	rollbackRebalance->srcSlot = pack.dstSlot;
+	rollbackRebalance->dstSlot = pack.srcSlot;
+	rollbackRebalance->count = pack.count;
+	rollbackPacks.push_back(std::move(rollbackRebalance));
+	success = true;
+}
+
+void PackRollbackGeneratorVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & pack)
+{
+	for(auto & subpack : pack.moves)
+		visitRebalanceStacks(subpack);
+
+	success = true;
+}
+
+void PackRollbackGeneratorVisitor::visitHeroVisitCastle(HeroVisitCastle & pack)
+{
+	auto rollbackVisit = std::make_unique<HeroVisitCastle>();
+	rollbackVisit->startVisit = !pack.startVisit;
+	rollbackVisit->tid = pack.tid;
+	rollbackVisit->hid = pack.hid;
+
+	rollbackPacks.push_back(std::move(rollbackVisit));
+
+	success = true;
+}
+
+void PackRollbackGeneratorVisitor::visitTryMoveHero(TryMoveHero & pack)
+{
+	auto rollbackMove = std::make_unique<TryMoveHero>();
+	auto rollbackFow = std::make_unique<FoWChange>();
+	const auto * movedHero = gs.getHero(pack.id);
+
+	rollbackMove->id = pack.id;
+	rollbackMove->movePoints = movedHero->movementPointsRemaining();
+	rollbackMove->result = pack.result;
+	if(pack.result == TryMoveHero::EMBARK)
+		rollbackMove->result = TryMoveHero::DISEMBARK;
+	if(pack.result == TryMoveHero::DISEMBARK)
+		rollbackMove->result = TryMoveHero::EMBARK;
+	rollbackMove->start = pack.end;
+	rollbackMove->end = pack.start;
+
+	rollbackFow->mode = ETileVisibility::HIDDEN;
+	rollbackFow->player = movedHero->getOwner();
+	rollbackFow->tiles = pack.fowRevealed;
+
+	rollbackPacks.push_back(std::move(rollbackMove));
+	rollbackPacks.push_back(std::move(rollbackFow));
+	success = true;
+}
+
+bool PackRollbackGeneratorVisitor::canBeRolledBack() const
+{
+	return success;
+}
+
+std::vector<std::unique_ptr<CPackForClient>> PackRollbackGeneratorVisitor::acquireRollbackPacks()
+{
+	return std::move(rollbackPacks);
+}

+ 117 - 0
client/netlag/PackRollbackGeneratorVisitor.h

@@ -0,0 +1,117 @@
+/*
+ * PackRollbackGeneratorVisitor.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/networkPacks/NetPackVisitor.h"
+
+/// Class that generates data for rollback
+/// on success, canBeRolledBack() method will return true
+/// and rollbackPacks will contain list of packs that can be applied to rollback provided pack
+/// Note that it is legal for rollbackPacks list to be empty for some trivial packs
+class PackRollbackGeneratorVisitor final : public ICPackVisitor
+{
+private:
+	const CGameState & gs;
+	std::vector<std::unique_ptr<CPackForClient>> rollbackPacks;
+	bool success = false;
+
+	void visitPackageReceived(PackageReceived & pack) override;
+	void visitPackageApplied(PackageApplied & pack) override;
+	void visitPlayerBlocked(PlayerBlocked & pack) override;
+	//void visitSetResources(SetResources & pack) override;
+	//void visitSetPrimarySkill(SetPrimarySkill & pack) override;
+	//void visitSetHeroExperience(SetHeroExperience & pack) override;
+	//void visitGiveStackExperience(GiveStackExperience & pack) override;
+	//void visitSetSecSkill(SetSecSkill & pack) override;
+	void visitHeroVisitCastle(HeroVisitCastle & pack) override;
+	//void visitSetMana(SetMana & pack) override;
+	//void visitSetMovePoints(SetMovePoints & pack) override;
+	//void visitSetResearchedSpells(SetResearchedSpells & pack) override;
+	//void visitFoWChange(FoWChange & pack) override;
+	//void visitChangeStackCount(ChangeStackCount & pack) override;
+	//void visitSetStackType(SetStackType & pack) override;
+	//void visitEraseStack(EraseStack & pack) override;
+	//void visitInsertNewStack(InsertNewStack & pack) override;
+	void visitSwapStacks(SwapStacks & pack) override;
+	void visitRebalanceStacks(RebalanceStacks & pack) override;
+	void visitBulkRebalanceStacks(BulkRebalanceStacks & pack) override;
+	//void visitGrowUpArtifact(GrowUpArtifact & pack) override;
+	//void visitPutArtifact(PutArtifact & pack) override;
+	//void visitBulkEraseArtifacts(BulkEraseArtifacts & pack) override;
+	//void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) override;
+	//void visitAssembledArtifact(AssembledArtifact & pack) override;
+	//void visitDisassembledArtifact(DisassembledArtifact & pack) override;
+	//void visitDischargeArtifact(DischargeArtifact & pack) override;
+	//void visitHeroVisit(HeroVisit & pack) override;
+	//void visitNewTurn(NewTurn & pack) override;
+	//void visitGiveBonus(GiveBonus & pack) override;
+	//void visitChangeObjPos(ChangeObjPos & pack) override;
+	//void visitPlayerEndsTurn(PlayerEndsTurn & pack) override;
+	//void visitPlayerEndsGame(PlayerEndsGame & pack) override;
+	//void visitRemoveBonus(RemoveBonus & pack) override;
+	//void visitRemoveObject(RemoveObject & pack) override;
+	void visitTryMoveHero(TryMoveHero & pack) override;
+	//void visitNewStructures(NewStructures & pack) override;
+	//void visitRazeStructures(RazeStructures & pack) override;
+	//void visitSetAvailableCreatures(SetAvailableCreatures & pack) override;
+	//void visitSetHeroesInTown(SetHeroesInTown & pack) override;
+	//void visitHeroRecruited(HeroRecruited & pack) override;
+	//void visitGiveHero(GiveHero & pack) override;
+	//void visitSetObjectProperty(SetObjectProperty & pack) override;
+	//void visitHeroLevelUp(HeroLevelUp & pack) override;
+	//void visitCommanderLevelUp(CommanderLevelUp & pack) override;
+	//void visitBattleStart(BattleStart & pack) override;
+	//void visitBattleSetActiveStack(BattleSetActiveStack & pack) override;
+	//void visitBattleTriggerEffect(BattleTriggerEffect & pack) override;
+	//void visitBattleAttack(BattleAttack & pack) override;
+	//void visitBattleSpellCast(BattleSpellCast & pack) override;
+	//void visitSetStackEffect(SetStackEffect & pack) override;
+	//void visitStacksInjured(StacksInjured & pack) override;
+	//void visitBattleUnitsChanged(BattleUnitsChanged & pack) override;
+	//void visitBattleObstaclesChanged(BattleObstaclesChanged & pack) override;
+	//void visitBattleStackMoved(BattleStackMoved & pack) override;
+	//void visitCatapultAttack(CatapultAttack & pack) override;
+	//void visitPlayerStartsTurn(PlayerStartsTurn & pack) override;
+	//void visitNewObject(NewObject & pack) override;
+	//void visitSetAvailableArtifacts(SetAvailableArtifacts & pack) override;
+	//void visitEntitiesChanged(EntitiesChanged & pack) override;
+	//void visitSetCommanderProperty(SetCommanderProperty & pack) override;
+	//void visitAddQuest(AddQuest & pack) override;
+	//void visitChangeFormation(ChangeFormation & pack) override;
+	//void visitChangeSpells(ChangeSpells & pack) override;
+	//void visitSetAvailableHero(SetAvailableHero & pack) override;
+	//void visitChangeObjectVisitors(ChangeObjectVisitors & pack) override;
+	//void visitChangeArtifactsCostume(ChangeArtifactsCostume & pack) override;
+	//void visitNewArtifact(NewArtifact & pack) override;
+	//void visitBattleUpdateGateState(BattleUpdateGateState & pack) override;
+	//void visitPlayerCheated(PlayerCheated & pack) override;
+	//void visitDaysWithoutTown(DaysWithoutTown & pack) override;
+	//void visitStartAction(StartAction & pack) override;
+	//void visitSetRewardableConfiguration(SetRewardableConfiguration & pack) override;
+	//void visitBattleSetStackProperty(BattleSetStackProperty & pack) override;
+	//void visitBattleNextRound(BattleNextRound & pack) override;
+	//void visitBattleCancelled(BattleCancelled & pack) override;
+	//void visitBattleResultsApplied(BattleResultsApplied & pack) override;
+	//void visitBattleResultAccepted(BattleResultAccepted & pack) override;
+	//void visitTurnTimeUpdate(TurnTimeUpdate & pack) override;
+
+public:
+	PackRollbackGeneratorVisitor(const CGameState & gs)
+		: gs(gs)
+	{
+	}
+
+	/// Returns true if tested pack can be rolled back
+	bool canBeRolledBack() const;
+
+	/// Acquires list of packs that can be used to rollback tested pack
+	/// (!) non-reentrable
+	std::vector<std::unique_ptr<CPackForClient>> acquireRollbackPacks();
+};

+ 32 - 31
docs/players/Cheat_Codes.md

@@ -133,46 +133,47 @@ Below a list of supported commands, with their arguments wrapped in `<>`
 
 #### Game Commands
 
-`die, fool` - quits game  
-`save <filename>` - saves game in given file (at the moment doesn't work)  
-`mp` - on adventure map with a hero selected, shows heroes current movement points, max movement points on land and on water  
-`bonuses` - shows bonuses of currently selected adventure map object
+- `die, fool` - quits game  
+- `save <filename>` - saves game in given file (at the moment doesn't work)  
+- `mp` - on adventure map with a hero selected, shows heroes current movement points, max movement points on land and on water  
+- `bonuses` - shows bonuses of currently selected adventure map object
 
 #### Extract commands
 
-`translate` - save game texts into json files
-`translate missing` - save untranslated game texts into json files
-`translate maps` - save map and campaign texts into json files
-`get config` - save game objects data into json files
-`get scripts` - dumps lua script stuff into files (currently inactive due to scripting disabled for default builds)
-`get txt` - save game texts into .txt files matching original heroes 3 files
-`def2bmp <.def file name>` - extract .def animation as BMP files
-`extract <relative file path>` - export file into directory used by other extraction commands
-`generate assets` - generate all assets at once
+- `translate` - save game texts into json files
+- `translate missing` - save untranslated game texts into json files
+- `translate maps` - save map and campaign texts into json files
+- `get config` - save game objects data into json files
+- `get scripts` - dumps lua script stuff into files (currently inactive due to scripting disabled for default builds)
+- `get txt` - save game texts into .txt files matching original heroes 3 files
+- `def2bmp <.def file name>` - extract .def animation as BMP files
+- `extract <relative file path>` - export file into directory used by other extraction commands
+- `generate assets` - generate all assets at once
 
 #### AI commands
 
-`setBattleAI <ai name>` - change battle AI used by neutral creatures to the one specified, persists through game quit  
-`gosolo` - AI takes over until the end of turn (unlike original H3 currently causes AI to take over until typed again)  
-`controlai <[red][blue][tan][green][orange][purple][teal][pink]>` - gives you control over specified AI player. If none is specified gives you control over all AI players  
-`autoskip` - Toggles autoskip mode on and off. In this mode, player turns are automatically skipped and only AI moves. However, GUI is still present and allows to observe AI moves. After this option is activated, you need to end first turn manually. Press `[Shift]` before your turn starts to not skip it  
+- `setBattleAI <ai name>` - change battle AI used by neutral creatures to the one specified, persists through game quit  
+- `gosolo` - AI takes over until the end of turn (unlike original H3 currently causes AI to take over until typed again)  
+- `controlai <[red][blue][tan][green][orange][purple][teal][pink]>` - gives you control over specified AI player. If none is specified gives you control over all AI players  
+- `autoskip` - Toggles autoskip mode on and off. In this mode, player turns are automatically skipped and only AI moves. However, GUI is still present and allows to observe AI moves. After this option is activated, you need to end first turn manually. Press `[Shift]` before your turn starts to not skip it  
 
 #### Settings
 
-`set <command> <on/off>` - sets special temporary settings that reset on game quit. Below some of the most notable commands:  
--`autoskip` - identical to `autoskip` option  
--`onlyAI` - run without human player, all players will be *default AI*  
--`headless` - run without GUI, implies `onlyAI` is set  
--`showGrid` - display a square grid overlay on top of adventure map  
--`showBlocked` - show blocked tiles on map  
--`showVisitable` - show visitable tiles on map  
--`hideSystemMessages` - suppress server messages in chat  
+- `set <command> <on/off>` - sets special temporary settings that reset on game quit. Below some of the most notable commands:  
+- `autoskip` - identical to `autoskip` option  
+- `onlyAI` - run without human player, all players will be *default AI*  
+- `headless` - run without GUI, implies `onlyAI` is set  
+- `showGrid` - display a square grid overlay on top of adventure map  
+- `showBlocked` - show blocked tiles on map  
+- `showVisitable` - show visitable tiles on map  
+- `hideSystemMessages` - suppress server messages in chat  
+- `antilag` - toggles network lag compensation in multiplayer on or off
 
 #### Developer Commands
 
-`crash` - force a game crash. It is sometimes useful to generate memory dump file in certain situations, for example game freeze  
-`gui` - displays tree view of currently present VCMI common GUI elements  
-`activate <0/1/2>` - activate game windows (no current use, apparently broken long ago)  
-`redraw` - force full graphical redraw  
-`screen` - show value of screenBuf variable, which prints "screen" when adventure map has current focus, "screen2" otherwise, and dumps values of both screen surfaces to .bmp files  
-`tell hs <hero ID> <artifact slot ID>` - write what artifact is present on artifact slot with specified ID for hero with specified ID. (must be called during gameplay)  
+- `crash` - force a game crash. It is sometimes useful to generate memory dump file in certain situations, for example game freeze  
+- `gui` - displays tree view of currently present VCMI common GUI elements  
+- `activate <0/1/2>` - activate game windows (no current use, apparently broken long ago)  
+- `redraw` - force full graphical redraw  
+- `screen` - show value of screenBuf variable, which prints "screen" when adventure map has current focus, "screen2" otherwise, and dumps values of both screen surfaces to .bmp files  
+- `tell hs <hero ID> <artifact slot ID>` - write what artifact is present on artifact slot with specified ID for hero with specified ID. (must be called during gameplay)  

+ 4 - 2
lib/CMakeLists.txt

@@ -247,7 +247,7 @@ set(lib_MAIN_SRCS
 
 	serializer/CLoadFile.cpp
 	serializer/CMemorySerializer.cpp
-	serializer/Connection.cpp
+	serializer/GameConnection.cpp
 	serializer/CSaveFile.cpp
 	serializer/CTypeList.cpp
 	serializer/JsonDeserializer.cpp
@@ -707,15 +707,17 @@ set(lib_MAIN_HEADERS
 	serializer/BinarySerializer.h
 	serializer/CLoadFile.h
 	serializer/CMemorySerializer.h
-	serializer/Connection.h
+	serializer/GameConnection.h
 	serializer/CSaveFile.h
 	serializer/CSerializer.h
 	serializer/CTypeList.h
+	serializer/GameConnectionID.h
 	serializer/JsonDeserializer.h
 	serializer/JsonSerializeFormat.h
 	serializer/JsonSerializer.h
 	serializer/JsonUpdater.h
 	serializer/ESerializationVersion.h
+	serializer/PlayerConnectionID.h
 	serializer/RegisterTypes.h
 	serializer/Serializeable.h
 	serializer/SerializerReflection.h

+ 13 - 13
lib/StartInfo.cpp

@@ -71,7 +71,7 @@ const PlayerSettings & StartInfo::getIthPlayersSettings(const PlayerColor & no)
 	return const_cast<StartInfo &>(*this).getIthPlayersSettings(no);
 }
 
-PlayerSettings * StartInfo::getPlayersSettings(const ui8 connectedPlayerId)
+PlayerSettings * StartInfo::getPlayersSettings(PlayerConnectionID connectedPlayerId)
 {
 	for(auto & elem : playerInfos)
 	{
@@ -124,7 +124,7 @@ void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const
 	}
 }
 
-bool LobbyInfo::isClientHost(int clientId) const
+bool LobbyInfo::isClientHost(GameConnectionID clientId) const
 {
 	return clientId == hostClientId;
 }
@@ -134,7 +134,7 @@ bool LobbyInfo::isPlayerHost(const PlayerColor & color) const
 	return vstd::contains(getAllClientPlayers(hostClientId), color);
 }
 
-std::set<PlayerColor> LobbyInfo::getAllClientPlayers(int clientId) const
+std::set<PlayerColor> LobbyInfo::getAllClientPlayers(GameConnectionID clientId) const
 {
 	std::set<PlayerColor> players;
 	for(auto & elem : si->playerInfos)
@@ -142,7 +142,7 @@ std::set<PlayerColor> LobbyInfo::getAllClientPlayers(int clientId) const
 		if(isClientHost(clientId) && elem.second.isControlledByAI())
 			players.insert(elem.first);
 
-		for(ui8 id : elem.second.connectedPlayerIDs)
+		for(PlayerConnectionID id : elem.second.connectedPlayerIDs)
 		{
 			if(vstd::contains(getConnectedPlayerIdsForClient(clientId), id))
 				players.insert(elem.first);
@@ -154,9 +154,9 @@ std::set<PlayerColor> LobbyInfo::getAllClientPlayers(int clientId) const
 	return players;
 }
 
-std::vector<ui8> LobbyInfo::getConnectedPlayerIdsForClient(int clientId) const
+std::vector<PlayerConnectionID> LobbyInfo::getConnectedPlayerIdsForClient(GameConnectionID clientId) const
 {
-	std::vector<ui8> ids;
+	std::vector<PlayerConnectionID> ids;
 
 	for(const auto & pair : playerNames)
 	{
@@ -172,12 +172,12 @@ std::vector<ui8> LobbyInfo::getConnectedPlayerIdsForClient(int clientId) const
 	return ids;
 }
 
-std::set<PlayerColor> LobbyInfo::clientHumanColors(int clientId)
+std::set<PlayerColor> LobbyInfo::clientHumanColors(GameConnectionID clientId)
 {
 	std::set<PlayerColor> players;
 	for(auto & elem : si->playerInfos)
 	{
-		for(ui8 id : elem.second.connectedPlayerIDs)
+		for(PlayerConnectionID id : elem.second.connectedPlayerIDs)
 		{
 			if(vstd::contains(getConnectedPlayerIdsForClient(clientId), id))
 			{
@@ -190,7 +190,7 @@ std::set<PlayerColor> LobbyInfo::clientHumanColors(int clientId)
 }
 
 
-PlayerColor LobbyInfo::clientFirstColor(int clientId) const
+PlayerColor LobbyInfo::clientFirstColor(GameConnectionID clientId) const
 {
 	for(auto & pair : si->playerInfos)
 	{
@@ -201,11 +201,11 @@ PlayerColor LobbyInfo::clientFirstColor(int clientId) const
 	return PlayerColor::CANNOT_DETERMINE;
 }
 
-bool LobbyInfo::isClientColor(int clientId, const PlayerColor & color) const
+bool LobbyInfo::isClientColor(GameConnectionID clientId, const PlayerColor & color) const
 {
 	if(si->playerInfos.find(color) != si->playerInfos.end())
 	{
-		for(ui8 id : si->playerInfos.find(color)->second.connectedPlayerIDs)
+		for(PlayerConnectionID id : si->playerInfos.find(color)->second.connectedPlayerIDs)
 		{
 			if(playerNames.find(id) != playerNames.end())
 			{
@@ -217,7 +217,7 @@ bool LobbyInfo::isClientColor(int clientId, const PlayerColor & color) const
 	return false;
 }
 
-ui8 LobbyInfo::clientFirstId(int clientId) const
+PlayerConnectionID LobbyInfo::clientFirstId(GameConnectionID clientId) const
 {
 	for(const auto & pair : playerNames)
 	{
@@ -225,7 +225,7 @@ ui8 LobbyInfo::clientFirstId(int clientId) const
 			return pair.first;
 	}
 
-	return 0;
+	throw std::runtime_error("LobbyInfo::clientFirstId: invalid GameConnectionID!");
 }
 
 PlayerInfo & LobbyInfo::getPlayerInfo(PlayerColor color)

+ 15 - 15
lib/StartInfo.h

@@ -15,7 +15,9 @@
 #include "TurnTimerInfo.h"
 #include "ExtraOptionsInfo.h"
 #include "campaign/CampaignConstants.h"
+#include "serializer/GameConnectionID.h"
 #include "serializer/Serializeable.h"
+#include "serializer/PlayerConnectionID.h"
 #include "ResourceSet.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -80,8 +82,6 @@ struct DLL_LINKAGE Handicap {
 /// Struct which describes the name, the color, the starting bonus of a player
 struct DLL_LINKAGE PlayerSettings
 {
-	enum { PLAYER_AI = 0 }; // for use in playerID
-
 	PlayerStartingBonus bonus;
 	FactionID castle;
 	HeroTypeID hero;
@@ -93,7 +93,7 @@ struct DLL_LINKAGE PlayerSettings
 	Handicap handicap;
 
 	std::string name;
-	std::set<ui8> connectedPlayerIDs; //Empty - AI, or connectrd player ids
+	std::set<PlayerConnectionID> connectedPlayerIDs; //Empty - AI, or connectrd player ids
 	bool compOnly; //true if this player is a computer only player; required for RMG
 	template <typename Handler>
 	void serialize(Handler &h)
@@ -148,7 +148,7 @@ struct DLL_LINKAGE StartInfo : public Serializeable
 
 	PlayerSettings & getIthPlayersSettings(const PlayerColor & no);
 	const PlayerSettings & getIthPlayersSettings(const PlayerColor & no) const;
-	PlayerSettings * getPlayersSettings(const ui8 connectedPlayerId);
+	PlayerSettings * getPlayersSettings(PlayerConnectionID connectedPlayerId);
 
 	// TODO: Must be client-side
 	std::string getCampaignName() const;
@@ -183,7 +183,7 @@ struct DLL_LINKAGE StartInfo : public Serializeable
 
 struct ClientPlayer
 {
-	int connection;
+	GameConnectionID connection;
 	std::string name;
 
 	template <typename Handler> void serialize(Handler &h)
@@ -197,14 +197,14 @@ struct DLL_LINKAGE LobbyState
 {
 	std::shared_ptr<StartInfo> si;
 	std::shared_ptr<CMapInfo> mi;
-	std::map<ui8, ClientPlayer> playerNames; // id of player <-> player name; 0 is reserved as ID of AI "players"
-	int hostClientId;
+	std::map<PlayerConnectionID, ClientPlayer> playerNames; // id of player <-> player name;
+	GameConnectionID hostClientId = GameConnectionID::INVALID;
 	// TODO: Campaign-only and we don't really need either of them.
 	// Before start both go into CCampaignState that is part of StartInfo
 	CampaignScenarioID campaignMap;
 	int campaignBonus;
 
-	LobbyState() : si(new StartInfo()), hostClientId(-1), campaignMap(CampaignScenarioID::NONE), campaignBonus(-1) {}
+	LobbyState() : si(new StartInfo()), campaignMap(CampaignScenarioID::NONE), campaignBonus(-1) {}
 
 	template <typename Handler> void serialize(Handler &h)
 	{
@@ -225,16 +225,16 @@ struct DLL_LINKAGE LobbyInfo : public LobbyState
 
 	void verifyStateBeforeStart(bool ignoreNoHuman = false) const;
 
-	bool isClientHost(int clientId) const;
+	bool isClientHost(GameConnectionID clientId) const;
 	bool isPlayerHost(const PlayerColor & color) const;
-	std::set<PlayerColor> getAllClientPlayers(int clientId) const;
-	std::vector<ui8> getConnectedPlayerIdsForClient(int clientId) const;
+	std::set<PlayerColor> getAllClientPlayers(GameConnectionID clientId) const;
+	std::vector<PlayerConnectionID> getConnectedPlayerIdsForClient(GameConnectionID clientId) const;
 
 	// Helpers for lobby state access
-	std::set<PlayerColor> clientHumanColors(int clientId);
-	PlayerColor clientFirstColor(int clientId) const;
-	bool isClientColor(int clientId, const PlayerColor & color) const;
-	ui8 clientFirstId(int clientId) const; // Used by chat only!
+	std::set<PlayerColor> clientHumanColors(GameConnectionID clientId);
+	PlayerColor clientFirstColor(GameConnectionID clientId) const;
+	bool isClientColor(GameConnectionID clientId, const PlayerColor & color) const;
+	PlayerConnectionID clientFirstId(GameConnectionID clientId) const; // Used by chat only!
 	PlayerInfo & getPlayerInfo(PlayerColor color);
 	TeamID getPlayerTeamId(const PlayerColor & color);
 };

+ 3 - 3
lib/callback/CGameInfoCallback.cpp

@@ -716,7 +716,7 @@ const TeamState * CGameInfoCallback::getPlayerTeam( PlayerColor color ) const
 	}
 }
 
-void CGameInfoCallback::getVisibleTilesInRange(std::unordered_set<int3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula) const
+void CGameInfoCallback::getVisibleTilesInRange(FowTilesType &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula) const
 {
 	gameState().getTilesInRange(tiles, pos, radious, ETileVisibility::REVEALED, *getPlayerID(),  distanceFormula);
 }
@@ -814,7 +814,7 @@ void CGameInfoCallback::getFreeTiles(std::vector<int3> & tiles) const
 	}
 }
 
-void CGameInfoCallback::getTilesInRange(std::unordered_set<int3> & tiles,
+void CGameInfoCallback::getTilesInRange(FowTilesType & tiles,
 											  const int3 & pos,
 											  int radious,
 											  ETileVisibility mode,
@@ -851,7 +851,7 @@ void CGameInfoCallback::getTilesInRange(std::unordered_set<int3> & tiles,
 	}
 }
 
-void CGameInfoCallback::getAllTiles(std::unordered_set<int3> & tiles, std::optional<PlayerColor> Player, int level, const std::function<bool(const TerrainTile *)> & filter) const
+void CGameInfoCallback::getAllTiles(FowTilesType & tiles, std::optional<PlayerColor> Player, int level, const std::function<bool(const TerrainTile *)> & filter) const
 {
 	if(Player.has_value() && !Player->isValidPlayer())
 	{

+ 3 - 3
lib/callback/CGameInfoCallback.h

@@ -75,7 +75,7 @@ public:
 	bool isTileGuardedUnchecked(int3 tile) const override;
 	const TerrainTile * getTile(int3 tile, bool verbose = true) const override;
 	const TerrainTile * getTileUnchecked(int3 tile) const override;
-	void getVisibleTilesInRange(std::unordered_set<int3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
+	void getVisibleTilesInRange(FowTilesType &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
 	void calculatePaths(const std::shared_ptr<PathfinderConfig> & config) const override;
 	EDiggingStatus getTileDigStatus(int3 tile, bool verbose = true) const override;
 	bool checkForVisitableDir(const int3 & src, const int3 & dst) const override;
@@ -103,8 +103,8 @@ public:
 
 	//used for random spawns
 	void getFreeTiles(std::vector<int3> &tiles) const;
-	void getTilesInRange(std::unordered_set<int3> & tiles, const int3 & pos, int radius, ETileVisibility mode, std::optional<PlayerColor> player = std::optional<PlayerColor>(), int3::EDistanceFormula formula = int3::DIST_2D) const override;
-	void getAllTiles(std::unordered_set<int3> &tiles, std::optional<PlayerColor> player, int level, const std::function<bool(const TerrainTile *)> & filter) const override;
+	void getTilesInRange(FowTilesType & tiles, const int3 & pos, int radius, ETileVisibility mode, std::optional<PlayerColor> player = std::optional<PlayerColor>(), int3::EDistanceFormula formula = int3::DIST_2D) const override;
+	void getAllTiles(FowTilesType &tiles, std::optional<PlayerColor> player, int level, const std::function<bool(const TerrainTile *)> & filter) const override;
 
 	void getAllowedSpells(std::vector<SpellID> &out, std::optional<ui16> level = std::nullopt) const;
 

+ 2 - 2
lib/callback/EditorCallback.cpp

@@ -97,12 +97,12 @@ std::vector<const CGObjectInstance*> EditorCallback::getGuardingCreatures(int3)
 	THROW_EDITOR_UNSUPPORTED;
 }
 
-void EditorCallback::getTilesInRange(std::unordered_set<int3> &, const int3 &, int, ETileVisibility, std::optional<PlayerColor>, int3::EDistanceFormula) const
+void EditorCallback::getTilesInRange(FowTilesType &, const int3 &, int, ETileVisibility, std::optional<PlayerColor>, int3::EDistanceFormula) const
 {
 	THROW_EDITOR_UNSUPPORTED;
 }
 
-void EditorCallback::getAllTiles(std::unordered_set<int3> &, std::optional<PlayerColor>, int, const std::function<bool(const TerrainTile *)> &) const
+void EditorCallback::getAllTiles(FowTilesType &, std::optional<PlayerColor>, int, const std::function<bool(const TerrainTile *)> &) const
 {
 	THROW_EDITOR_UNSUPPORTED;
 }

+ 2 - 2
lib/callback/EditorCallback.h

@@ -40,8 +40,8 @@ public:
 	bool checkForVisitableDir(const int3 & src, const int3 & dst) const override;
 	std::vector<const CGObjectInstance*> getGuardingCreatures(int3 pos) const override;
 
-	void getTilesInRange(std::unordered_set<int3> & tiles, const int3 & pos, int radius, ETileVisibility mode, std::optional<PlayerColor> player, int3::EDistanceFormula formula) const override;
-	void getAllTiles(std::unordered_set<int3> &tiles, std::optional<PlayerColor> player, int level, const std::function<bool(const TerrainTile *)> & filter) const override;
+	void getTilesInRange(FowTilesType & tiles, const int3 & pos, int radius, ETileVisibility mode, std::optional<PlayerColor> player, int3::EDistanceFormula formula) const override;
+	void getAllTiles(FowTilesType &tiles, std::optional<PlayerColor> player, int level, const std::function<bool(const TerrainTile *)> & filter) const override;
 
 	std::vector<ObjectInstanceID> getVisibleTeleportObjects(std::vector<ObjectInstanceID> ids, PlayerColor player) const override;
 	std::vector<ObjectInstanceID> getTeleportChannelEntrances(TeleportChannelID id, PlayerColor player) const override;

+ 2 - 1
lib/callback/IGameEventCallback.h

@@ -34,6 +34,7 @@ class CCreatureSet;
 class CGObjectInstance;
 class IObjectInterface;
 
+using FowTilesType = std::set<int3>;
 enum class EOpenWindowMode : uint8_t;
 
 namespace Rewardable
@@ -111,7 +112,7 @@ public:
 	virtual void sendAndApply(CPackForClient & pack) = 0;
 	virtual void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)=0; //when two heroes meet on adventure map
 	virtual void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, ETileVisibility mode) = 0;
-	virtual void changeFogOfWar(const std::unordered_set<int3> &tiles, PlayerColor player, ETileVisibility mode) = 0;
+	virtual void changeFogOfWar(const FowTilesType &tiles, PlayerColor player, ETileVisibility mode) = 0;
 
 	virtual void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) = 0;
 

+ 3 - 2
lib/callback/IGameEventsReceiver.h

@@ -32,6 +32,7 @@ class EVictoryLossCheckResult;
 class IShipyard;
 class IMarket;
 
+using FowTilesType = std::set<int3>;
 enum class EInfoWindowMode : uint8_t;
 
 class DLL_LINKAGE IGameEventsReceiver
@@ -76,8 +77,8 @@ public:
 	virtual void showTavernWindow(const CGObjectInstance * object, const CGHeroInstance * visitor, QueryID queryID) {};
 	virtual void showQuestLog(){};
 	virtual void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID){}; //called when a hero casts a spell
-	virtual void tileHidden(const std::unordered_set<int3> &pos){};
-	virtual void tileRevealed(const std::unordered_set<int3> &pos){};
+	virtual void tileHidden(const FowTilesType &pos){};
+	virtual void tileRevealed(const FowTilesType &pos){};
 	virtual void newObject(const CGObjectInstance * obj){}; //eg. ship built in shipyard
 	virtual void availableArtifactsChanged(const CGBlackMarket *bm = nullptr){}; //bm may be nullptr, then artifacts are changed in the global pool (used by merchants in towns)
 	virtual void centerView (int3 pos, int focusTime){};

+ 4 - 2
lib/callback/IGameInfoCallback.h

@@ -40,6 +40,8 @@ class CGTeleport;
 class CGTownInstance;
 class IMarket;
 
+using FowTilesType = std::set<int3>;
+
 #if SCRIPTING_ENABLED
 namespace scripting
 {
@@ -151,10 +153,10 @@ public:
 	virtual bool isTileGuardedUnchecked(int3 tile) const = 0;
 
 	/// Returns all tiles within specified range with specific tile visibility mode
-	virtual void getTilesInRange(std::unordered_set<int3> & tiles, const int3 & pos, int radius, ETileVisibility mode, std::optional<PlayerColor> player = std::optional<PlayerColor>(), int3::EDistanceFormula formula = int3::DIST_2D) const = 0;
+	virtual void getTilesInRange(FowTilesType & tiles, const int3 & pos, int radius, ETileVisibility mode, std::optional<PlayerColor> player = std::optional<PlayerColor>(), int3::EDistanceFormula formula = int3::DIST_2D) const = 0;
 
 	/// returns all tiles on given level (-1 - both levels, otherwise number of level)
-	virtual void getAllTiles(std::unordered_set<int3> &tiles, std::optional<PlayerColor> player, int level, const std::function<bool(const TerrainTile *)> & filter) const = 0;
+	virtual void getAllTiles(FowTilesType &tiles, std::optional<PlayerColor> player, int level, const std::function<bool(const TerrainTile *)> & filter) const = 0;
 
 	virtual std::vector<ObjectInstanceID> getVisibleTeleportObjects(std::vector<ObjectInstanceID> ids, PlayerColor player)  const  = 0;
 	virtual std::vector<ObjectInstanceID> getTeleportChannelEntrances(TeleportChannelID id, PlayerColor Player = PlayerColor::UNFLAGGABLE) const  = 0;

+ 7 - 7
lib/gameState/CGameState.cpp

@@ -280,15 +280,15 @@ void CGameState::updateEntity(Metatype metatype, int32_t index, const JsonNode &
 	}
 }
 
-void CGameState::updateOnLoad(StartInfo * si)
+void CGameState::updateOnLoad(const StartInfo & si)
 {
 	assert(services);
-	scenarioOps->playerInfos = si->playerInfos;
-	for(auto & i : si->playerInfos)
+	scenarioOps->playerInfos = si.playerInfos;
+	for(auto & i : si.playerInfos)
 		players.at(i.first).human = i.second.isControlledByHuman();
-	scenarioOps->extraOptionsInfo = si->extraOptionsInfo;
-	scenarioOps->turnTimerInfo = si->turnTimerInfo;
-	scenarioOps->simturnsInfo = si->simturnsInfo;
+	scenarioOps->extraOptionsInfo = si.extraOptionsInfo;
+	scenarioOps->turnTimerInfo = si.turnTimerInfo;
+	scenarioOps->simturnsInfo = si.simturnsInfo;
 }
 
 void CGameState::initNewGame(const IMapService * mapService, vstd::RNG & randomGenerator, bool allowSavingRandomMap, Load::ProgressAccumulator & progressTracking)
@@ -676,7 +676,7 @@ void CGameState::initFogOfWar()
 			if(!vstd::contains(elem.second.players, obj->getOwner()))
 				continue; //not a flagged object
 
-			std::unordered_set<int3> tiles;
+			FowTilesType tiles;
 			getTilesInRange(tiles, obj->getSightCenter(), obj->getSightRadius(), ETileVisibility::HIDDEN, obj->tempOwner);
 			for(const int3 & tile : tiles)
 			{

+ 1 - 1
lib/gameState/CGameState.h

@@ -75,7 +75,7 @@ public:
 	void preInit(Services * services);
 
 	void init(const IMapService * mapService, StartInfo * si, IGameRandomizer & gameRandomizer, Load::ProgressAccumulator &, bool allowSavingRandomMap = true);
-	void updateOnLoad(StartInfo * si);
+	void updateOnLoad(const StartInfo & si);
 
 	ui32 day; //total number of days in game
 	std::map<PlayerColor, PlayerState> players;

+ 1 - 14
lib/gameState/GameStatePackVisitor.cpp

@@ -189,7 +189,7 @@ void GameStatePackVisitor::visitFoWChange(FoWChange & pack)
 
 	if (pack.mode == ETileVisibility::HIDDEN) //do not hide too much
 	{
-		std::unordered_set<int3> tilesRevealed;
+		FowTilesType tilesRevealed;
 		for (auto & o : gs.getMap().getObjects())
 		{
 			if (o->asOwnable())
@@ -321,19 +321,6 @@ void GameStatePackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
 	gs.actingPlayers.erase(pack.player);
 }
 
-void GameStatePackVisitor::visitPlayerReinitInterface(PlayerReinitInterface & pack)
-{
-	if(!gs.getStartInfo())
-		return;
-
-	//TODO: what does mean if more that one player connected?
-	if(pack.playerConnectionId == PlayerSettings::PLAYER_AI)
-	{
-		for(const auto & player : pack.players)
-			gs.getStartInfo()->getIthPlayersSettings(player).connectedPlayerIDs.clear();
-	}
-}
-
 void GameStatePackVisitor::visitRemoveBonus(RemoveBonus & pack)
 {
 	CBonusSystemNode *node = nullptr;

+ 0 - 1
lib/gameState/GameStatePackVisitor.h

@@ -57,7 +57,6 @@ public:
 	void visitChangeObjPos(ChangeObjPos & pack) override;
 	void visitPlayerEndsTurn(PlayerEndsTurn & pack) override;
 	void visitPlayerEndsGame(PlayerEndsGame & pack) override;
-	void visitPlayerReinitInterface(PlayerReinitInterface & pack) override;
 	void visitRemoveBonus(RemoveBonus & pack) override;
 	void visitRemoveObject(RemoveObject & pack) override;
 	void visitTryMoveHero(TryMoveHero & pack) override;

+ 3 - 3
lib/network/NetworkConnection.cpp

@@ -209,14 +209,14 @@ void NetworkConnection::close()
 }
 
 InternalConnection::InternalConnection(INetworkConnectionListener & listener, NetworkContext & context)
-	: io(context)
+	: context(context)
 	, listener(listener)
 {
 }
 
 void InternalConnection::receivePacket(const std::vector<std::byte> & message)
 {
-	boost::asio::post(io, [self = std::static_pointer_cast<InternalConnection>(shared_from_this()), message](){
+	boost::asio::post(context, [self = std::static_pointer_cast<InternalConnection>(shared_from_this()), message](){
 		if (self->connectionActive)
 			self->listener.onPacketReceived(self, message);
 	});
@@ -224,7 +224,7 @@ void InternalConnection::receivePacket(const std::vector<std::byte> & message)
 
 void InternalConnection::disconnect()
 {
-	boost::asio::post(io, [self = std::static_pointer_cast<InternalConnection>(shared_from_this())](){
+	boost::asio::post(context, [self = std::static_pointer_cast<InternalConnection>(shared_from_this())](){
 		self->listener.onDisconnected(self, "Internal connection has been terminated");
 		self->otherSideWeak.reset();
 		self->connectionActive = false;

+ 1 - 1
lib/network/NetworkConnection.h

@@ -49,7 +49,7 @@ public:
 class InternalConnection final : public IInternalConnection, public std::enable_shared_from_this<InternalConnection>
 {
 	std::weak_ptr<IInternalConnection> otherSideWeak;
-	NetworkContext & io;
+	NetworkContext & context;
 	INetworkConnectionListener & listener;
 	bool connectionActive = false;
 public:

+ 18 - 11
lib/network/NetworkHandler.cpp

@@ -21,18 +21,25 @@ std::unique_ptr<INetworkHandler> INetworkHandler::createHandler()
 }
 
 NetworkHandler::NetworkHandler()
-	: io(std::make_unique<NetworkContext>())
+	: context(std::make_unique<NetworkContext>())
 {}
 
 std::unique_ptr<INetworkServer> NetworkHandler::createServerTCP(INetworkServerListener & listener)
 {
-	return std::make_unique<NetworkServer>(listener, *io);
+	return std::make_unique<NetworkServer>(listener, *context);
+}
+
+std::shared_ptr<INetworkConnection> NetworkHandler::createAsyncConnection(INetworkConnectionListener & listener)
+{
+	auto loopbackConnection = std::make_shared<InternalConnection>(listener, *context);
+	loopbackConnection->connectTo(loopbackConnection);
+	return loopbackConnection;
 }
 
 void NetworkHandler::connectToRemote(INetworkClientListener & listener, const std::string & host, uint16_t port)
 {
-	auto socket = std::make_shared<NetworkSocket>(*io);
-	auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(*io);
+	auto socket = std::make_shared<NetworkSocket>(*context);
+	auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(*context);
 
 	resolver->async_resolve(host, std::to_string(port),
 	[this, &listener, resolver, socket](const boost::system::error_code& error, const boost::asio::ip::tcp::resolver::results_type & endpoints)
@@ -50,7 +57,7 @@ void NetworkHandler::connectToRemote(INetworkClientListener & listener, const st
 				listener.onConnectionFailed(error.message());
 				return;
 			}
-			auto connection = std::make_shared<NetworkConnection>(listener, socket, *io);
+			auto connection = std::make_shared<NetworkConnection>(listener, socket, *context);
 			connection->start();
 
 			listener.onConnectionEstablished(connection);
@@ -60,13 +67,13 @@ void NetworkHandler::connectToRemote(INetworkClientListener & listener, const st
 
 void NetworkHandler::run()
 {
-	boost::asio::executor_work_guard<decltype(io->get_executor())> work{io->get_executor()};
-	io->run();
+	boost::asio::executor_work_guard<decltype(context->get_executor())> work{context->get_executor()};
+	context->run();
 }
 
 void NetworkHandler::createTimer(INetworkTimerListener & listener, std::chrono::milliseconds duration)
 {
-	auto timer = std::make_shared<NetworkTimer>(*io, duration);
+	auto timer = std::make_shared<NetworkTimer>(*context, duration);
 	timer->async_wait([&listener, timer](const boost::system::error_code& error){
 		if (!error)
 			listener.onTimer();
@@ -75,18 +82,18 @@ void NetworkHandler::createTimer(INetworkTimerListener & listener, std::chrono::
 
 void NetworkHandler::createInternalConnection(INetworkClientListener & listener, INetworkServer & server)
 {
-	auto localConnection = std::make_shared<InternalConnection>(listener, *io);
+	auto localConnection = std::make_shared<InternalConnection>(listener, *context);
 
 	server.receiveInternalConnection(localConnection);
 
-	boost::asio::post(*io, [&listener, localConnection](){
+	boost::asio::post(*context, [&listener, localConnection](){
 		listener.onConnectionEstablished(localConnection);
 	});
 }
 
 void NetworkHandler::stop()
 {
-	io->stop();
+	context->stop();
 }
 
 VCMI_LIB_NAMESPACE_END

+ 3 - 2
lib/network/NetworkHandler.h

@@ -13,9 +13,9 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class NetworkHandler : public INetworkHandler
+class NetworkHandler final : public INetworkHandler
 {
-	std::unique_ptr<NetworkContext> io;
+	std::unique_ptr<NetworkContext> context;
 
 public:
 	NetworkHandler();
@@ -23,6 +23,7 @@ public:
 	std::unique_ptr<INetworkServer> createServerTCP(INetworkServerListener & listener) override;
 	void connectToRemote(INetworkClientListener & listener, const std::string & host, uint16_t port) override;
 	void createInternalConnection(INetworkClientListener & listener, INetworkServer & server) override;
+	std::shared_ptr<INetworkConnection> createAsyncConnection(INetworkConnectionListener & listener) override;
 	void createTimer(INetworkTimerListener & listener, std::chrono::milliseconds duration) override;
 
 	void run() override;

+ 3 - 0
lib/network/NetworkInterface.h

@@ -108,6 +108,9 @@ public:
 	/// On success INetworkTimerListener::onConnectionEstablished() will be called asynchronously, established connection provided as parameter
 	virtual void createInternalConnection(INetworkClientListener & listener, INetworkServer & server) = 0;
 
+	/// Creates one-way connection that allows sending messages to listener in async form
+	virtual std::shared_ptr<INetworkConnection> createAsyncConnection(INetworkConnectionListener & listener) = 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

+ 5 - 5
lib/network/NetworkServer.cpp

@@ -14,20 +14,20 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 NetworkServer::NetworkServer(INetworkServerListener & listener, NetworkContext & context)
-	: io(context)
+	: context(context)
 	, listener(listener)
 {
 }
 
 uint16_t NetworkServer::start(uint16_t port)
 {
-	acceptor = std::make_shared<NetworkAcceptor>(io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
+	acceptor = std::make_shared<NetworkAcceptor>(context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
 	return startAsyncAccept();
 }
 
 uint16_t NetworkServer::startAsyncAccept()
 {
-	auto upcomingConnection = std::make_shared<NetworkSocket>(io);
+	auto upcomingConnection = std::make_shared<NetworkSocket>(context);
 	acceptor->async_accept(*upcomingConnection, [this, upcomingConnection](const auto & ec) { connectionAccepted(upcomingConnection, ec); });
 	return acceptor->local_endpoint().port();
 }
@@ -40,7 +40,7 @@ void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingCo
 	}
 
 	logNetwork->info("We got a new connection! :)");
-	auto connection = std::make_shared<NetworkConnection>(*this, upcomingConnection, io);
+	auto connection = std::make_shared<NetworkConnection>(*this, upcomingConnection, context);
 	connections.insert(connection);
 	connection->start();
 	listener.onNewConnection(connection);
@@ -59,7 +59,7 @@ void NetworkServer::onDisconnected(const std::shared_ptr<INetworkConnection> & c
 
 void NetworkServer::receiveInternalConnection(std::shared_ptr<IInternalConnection> remoteConnection)
 {
-	auto localConnection = std::make_shared<InternalConnection>(*this, io);
+	auto localConnection = std::make_shared<InternalConnection>(*this, context);
 
 	connections.insert(localConnection);
 

+ 1 - 1
lib/network/NetworkServer.h

@@ -15,7 +15,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 class NetworkServer : public INetworkConnectionListener, public INetworkServer
 {
-	NetworkContext & io;
+	NetworkContext & context;
 	std::shared_ptr<NetworkAcceptor> acceptor;
 	std::set<std::shared_ptr<INetworkConnection>> connections;
 

+ 1 - 1
lib/networkPacks/NetPackVisitor.h

@@ -28,6 +28,7 @@ public:
 	virtual void visitForServer(CPackForServer & pack) {}
 	virtual void visitForClient(CPackForClient & pack) {}
 	virtual void visitPackageApplied(PackageApplied & pack) {}
+	virtual void visitPackageReceived(PackageReceived & pack) {}
 	virtual void visitSystemMessage(SystemMessage & pack) {}
 	virtual void visitPlayerBlocked(PlayerBlocked & pack) {}
 	virtual void visitPlayerCheated(PlayerCheated & pack) {}
@@ -53,7 +54,6 @@ public:
 	virtual void visitChangeObjPos(ChangeObjPos & pack) {}
 	virtual void visitPlayerEndsTurn(PlayerEndsTurn & pack) {};
 	virtual void visitPlayerEndsGame(PlayerEndsGame & pack) {}
-	virtual void visitPlayerReinitInterface(PlayerReinitInterface & pack) {}
 	virtual void visitRemoveBonus(RemoveBonus & pack) {}
 	virtual void visitSetCommanderProperty(SetCommanderProperty & pack) {}
 	virtual void visitAddQuest(AddQuest & pack) {}

+ 0 - 2
lib/networkPacks/NetPacksBase.h

@@ -15,8 +15,6 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CGameState;
-class CConnection;
-
 class ICPackVisitor;
 
 struct DLL_LINKAGE CPack : public Serializeable

+ 5 - 5
lib/networkPacks/NetPacksLib.cpp

@@ -74,6 +74,11 @@ void PackageApplied::visitTyped(ICPackVisitor & visitor)
 	visitor.visitPackageApplied(*this);
 }
 
+void PackageReceived::visitTyped(ICPackVisitor & visitor)
+{
+	visitor.visitPackageReceived(*this);
+}
+
 void SystemMessage::visitTyped(ICPackVisitor & visitor)
 {
 	visitor.visitSystemMessage(*this);
@@ -188,11 +193,6 @@ void PlayerEndsGame::visitTyped(ICPackVisitor & visitor)
 	visitor.visitPlayerEndsGame(*this);
 }
 
-void PlayerReinitInterface::visitTyped(ICPackVisitor & visitor)
-{
-	visitor.visitPlayerReinitInterface(*this);
-}
-
 void RemoveBonus::visitTyped(ICPackVisitor & visitor)
 {
 	visitor.visitRemoveBonus(*this);

+ 52 - 23
lib/networkPacks/PacksForClient.h

@@ -48,27 +48,64 @@ class BattleInfo;
 // For now it's will be there till teleports code refactored and moved into own file
 using TTeleportExitsList = std::vector<std::pair<ObjectInstanceID, int3>>;
 
+using FowTilesType = std::set<int3>;
+
 /***********************************************************************************************************/
 struct DLL_LINKAGE PackageApplied : public CPackForClient
 {
 	PackageApplied() = default;
-	explicit PackageApplied(ui8 Result)
-		: result(Result)
+	PackageApplied(PlayerColor player, uint32_t requestID, uint16_t packType, bool result)
+		: player(player)
+		, requestID(requestID)
+		, packType(packType)
+		, result(result)
 	{
 	}
+
 	void visitTyped(ICPackVisitor & visitor) override;
 
-	ui8 result = 0; //0 - something went wrong, request hasn't been realized; 1 - OK
-	ui32 packType = 0; //type id of applied package
-	ui32 requestID = 0; //an ID given by client to the request that was applied
+	/// ID of player that sent this package
 	PlayerColor player;
+	/// request ID, as provided by player
+	uint32_t requestID = 0;
+	/// type id of applied package
+	uint16_t packType = 0;
+	/// If false, then pack failed to apply, for example - illegal request
+	bool result = false;
 
 	template <typename Handler> void serialize(Handler & h)
 	{
-		h & result;
-		h & packType;
+		h & player;
 		h & requestID;
+		h & packType;
+		h & result;
+	}
+};
+
+struct DLL_LINKAGE PackageReceived : public CPackForClient
+{
+	PackageReceived() = default;
+	PackageReceived(PlayerColor player, uint32_t requestID, uint16_t packType)
+		: player(player)
+		, requestID(requestID)
+		, packType(packType)
+	{
+	}
+
+	void visitTyped(ICPackVisitor & visitor) override;
+
+	/// ID of player that sent this package
+	PlayerColor player;
+	/// request ID, as provided by player
+	uint32_t requestID;
+	/// type id of applied package
+	uint16_t packType;
+
+	template <typename Handler> void serialize(Handler & h)
+	{
 		h & player;
+		h & requestID;
+		h & packType;
 	}
 };
 
@@ -370,7 +407,7 @@ struct DLL_LINKAGE SetMovePoints : public CPackForClient
 
 struct DLL_LINKAGE FoWChange : public CPackForClient
 {
-	std::unordered_set<int3> tiles;
+	FowTilesType tiles;
 	PlayerColor player;
 	ETileVisibility mode;
 	bool waitForDialogs = false;
@@ -498,20 +535,6 @@ struct DLL_LINKAGE PlayerEndsGame : public CPackForClient
 	}
 };
 
-struct DLL_LINKAGE PlayerReinitInterface : public CPackForClient
-{
-	std::vector<PlayerColor> players;
-	ui8 playerConnectionId; //PLAYER_AI for AI player
-
-	void visitTyped(ICPackVisitor & visitor) override;
-
-	template <typename Handler> void serialize(Handler & h)
-	{
-		h & players;
-		h & playerConnectionId;
-	}
-};
-
 struct DLL_LINKAGE RemoveBonus : public CPackForClient
 {
 	explicit RemoveBonus(GiveBonus::ETarget Who = GiveBonus::ETarget::OBJECT)
@@ -638,7 +661,7 @@ struct DLL_LINKAGE TryMoveHero : public CPackForClient
 	/// Hero anchor position to which hero moves
 	int3 end;
 	/// Tiles that were revealed by this move
-	std::unordered_set<int3> fowRevealed;
+	FowTilesType fowRevealed;
 	/// If hero moves on guarded tile, this field will be set to visitable pos of attacked wandering monster
 	int3 attackedFrom;
 
@@ -658,6 +681,12 @@ struct DLL_LINKAGE TryMoveHero : public CPackForClient
 		h & movePoints;
 		h & fowRevealed;
 		h & attackedFrom;
+
+		std::string fow;
+		for (const auto & tile : fowRevealed)
+			fow += tile.toString() + ", ";
+
+		logGlobal->info("OI %d, mp %d, res %d, start %s, end %s, attack %s, fow %s", id.getNum(), movePoints, static_cast<int>(result), start.toString(), end.toString(), attackedFrom.toString(), fow);
 	}
 };
 

+ 5 - 8
lib/networkPacks/PacksForLobby.h

@@ -41,8 +41,8 @@ struct DLL_LINKAGE LobbyClientConnected : public CLobbyPackToPropagate
 	std::vector<std::string> names;
 	EStartMode mode = EStartMode::INVALID;
 	// Changed by server before announcing pack
-	int clientId = -1;
-	int hostClientId = -1;
+	GameConnectionID clientId = GameConnectionID::INVALID;
+	GameConnectionID hostClientId = GameConnectionID::INVALID;
 	ESerializationVersion version = ESerializationVersion::CURRENT;
 
 	void visitTyped(ICPackVisitor & visitor) override;
@@ -61,10 +61,9 @@ struct DLL_LINKAGE LobbyClientConnected : public CLobbyPackToPropagate
 
 struct DLL_LINKAGE LobbyClientDisconnected : public CLobbyPackToPropagate
 {
-	int clientId;
+	GameConnectionID clientId = GameConnectionID::INVALID;
 	bool shutdownServer = false;
 
-
 	void visitTyped(ICPackVisitor & visitor) override;
 
 	template <typename Handler> void serialize(Handler &h)
@@ -138,7 +137,6 @@ struct DLL_LINKAGE LobbyStartGame : public CLobbyPackToPropagate
 	// Set by server
 	std::shared_ptr<StartInfo> initializedStartInfo;
 	std::shared_ptr<CGameState> initializedGameState;
-	int clientId = -1; //-1 means to all clients
 
 	void visitTyped(ICPackVisitor & visitor) override;
 
@@ -146,7 +144,6 @@ struct DLL_LINKAGE LobbyStartGame : public CLobbyPackToPropagate
 	{
 		if (!h.saving)
 			h.loadingGamestate = true;
-		h & clientId;
 		h & initializedStartInfo;
 		h & initializedGameState;
 		if (!h.saving)
@@ -156,7 +153,7 @@ struct DLL_LINKAGE LobbyStartGame : public CLobbyPackToPropagate
 
 struct DLL_LINKAGE LobbyChangeHost : public CLobbyPackToPropagate
 {
-	int newHostConnectionId = -1;
+	GameConnectionID newHostConnectionId = GameConnectionID::INVALID;
 
 	void visitTyped(ICPackVisitor & visitor) override;
 
@@ -340,7 +337,7 @@ struct DLL_LINKAGE LobbySetDifficulty : public CLobbyPackToServer
 
 struct DLL_LINKAGE LobbyForceSetPlayer : public CLobbyPackToServer
 {
-	ui8 targetConnectedPlayer = -1;
+	PlayerConnectionID targetConnectedPlayer = PlayerConnectionID::INVALID;
 	PlayerColor targetPlayerColor = PlayerColor::CANNOT_DETERMINE;
 
 	void visitTyped(ICPackVisitor & visitor) override;

+ 3 - 1
lib/pathfinder/CPathfinder.h

@@ -20,6 +20,8 @@ class TurnInfo;
 class CGTeleport;
 struct PathfinderOptions;
 
+using FowTilesType = std::set<int3>;
+
 // Optimized storage - tile can have 0-8 neighbour tiles
 // static_vector uses fixed, preallocated storage (capacity) and dynamic size
 // this avoid dynamic allocations on huge number of neighbour list queries
@@ -78,7 +80,7 @@ public:
 		PATROL_LOCKED = 1,
 		PATROL_RADIUS
 	} patrolState;
-	std::unordered_set<int3> patrolTiles;
+	FowTilesType patrolTiles;
 
 	int turn;
 	PlayerColor owner;

+ 1 - 1
lib/rewardable/Interface.cpp

@@ -76,7 +76,7 @@ void Rewardable::Interface::grantRewardBeforeLevelup(IGameEventCallback & gameEv
 			return score > 0;
 		};
 
-		std::unordered_set<int3> tiles;
+		FowTilesType tiles;
 		if (props.radius > 0)
 		{
 			cb->getTilesInRange(tiles, hero->getSightCenter(), props.radius, ETileVisibility::HIDDEN, hero->getOwner());

+ 17 - 18
lib/serializer/Connection.cpp → lib/serializer/GameConnection.cpp

@@ -1,5 +1,5 @@
 /*
- * Connection.cpp, part of VCMI engine
+ * GameConnection.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -8,7 +8,7 @@
  *
  */
 #include "StdInc.h"
-#include "Connection.h"
+#include "GameConnection.h"
 
 #include "BinaryDeserializer.h"
 #include "BinarySerializer.h"
@@ -19,7 +19,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class DLL_LINKAGE ConnectionPackWriter final : public IBinaryWriter
+class GameConnectionPackWriter final : public IBinaryWriter
 {
 public:
 	std::vector<std::byte> buffer;
@@ -27,7 +27,7 @@ public:
 	int write(const std::byte * data, unsigned size) final;
 };
 
-class DLL_LINKAGE ConnectionPackReader final : public IBinaryReader
+class GameConnectionPackReader final : public IBinaryReader
 {
 public:
 	const std::vector<std::byte> * buffer;
@@ -36,13 +36,13 @@ public:
 	int read(std::byte * data, unsigned size) final;
 };
 
-int ConnectionPackWriter::write(const std::byte * data, unsigned size)
+int GameConnectionPackWriter::write(const std::byte * data, unsigned size)
 {
 	buffer.insert(buffer.end(), data, data + size);
 	return size;
 }
 
-int ConnectionPackReader::read(std::byte * data, unsigned size)
+int GameConnectionPackReader::read(std::byte * data, unsigned size)
 {
 	if (position + size > buffer->size())
 		throw std::runtime_error("End of file reached when reading received network pack!");
@@ -52,13 +52,12 @@ int ConnectionPackReader::read(std::byte * data, unsigned size)
 	return size;
 }
 
-CConnection::CConnection(std::weak_ptr<INetworkConnection> networkConnection)
+GameConnection::GameConnection(std::weak_ptr<INetworkConnection> networkConnection)
 	: networkConnection(networkConnection)
-	, packReader(std::make_unique<ConnectionPackReader>())
-	, packWriter(std::make_unique<ConnectionPackWriter>())
+	, packReader(std::make_unique<GameConnectionPackReader>())
+	, packWriter(std::make_unique<GameConnectionPackWriter>())
 	, deserializer(std::make_unique<BinaryDeserializer>(packReader.get()))
 	, serializer(std::make_unique<BinarySerializer>(packWriter.get()))
-	, connectionID(-1)
 {
 	assert(networkConnection.lock() != nullptr);
 
@@ -66,9 +65,9 @@ CConnection::CConnection(std::weak_ptr<INetworkConnection> networkConnection)
 	deserializer->version = ESerializationVersion::CURRENT;
 }
 
-CConnection::~CConnection() = default;
+GameConnection::~GameConnection() = default;
 
-void CConnection::sendPack(const CPack & pack)
+void GameConnection::sendPack(const CPack & pack)
 {
 	std::scoped_lock lock(writeMutex);
 
@@ -87,7 +86,7 @@ void CConnection::sendPack(const CPack & pack)
 	serializer->clear();
 }
 
-std::unique_ptr<CPack> CConnection::retrievePack(const std::vector<std::byte> & data)
+std::unique_ptr<CPack> GameConnection::retrievePack(const std::vector<std::byte> & data)
 {
 	std::unique_ptr<CPack> result;
 
@@ -108,28 +107,28 @@ std::unique_ptr<CPack> CConnection::retrievePack(const std::vector<std::byte> &
 	return result;
 }
 
-bool CConnection::isMyConnection(const std::shared_ptr<INetworkConnection> & otherConnection) const
+bool GameConnection::isMyConnection(const std::shared_ptr<INetworkConnection> & otherConnection) const
 {
 	return otherConnection != nullptr && networkConnection.lock() == otherConnection;
 }
 
-std::shared_ptr<INetworkConnection> CConnection::getConnection()
+std::shared_ptr<INetworkConnection> GameConnection::getConnection()
 {
 	return networkConnection.lock();
 }
 
-void CConnection::enterLobbyConnectionMode()
+void GameConnection::enterLobbyConnectionMode()
 {
 	deserializer->clear();
 	serializer->clear();
 }
 
-void CConnection::setCallback(IGameInfoCallback & cb)
+void GameConnection::setCallback(IGameInfoCallback & cb)
 {
 	deserializer->cb = &cb;
 }
 
-void CConnection::setSerializationVersion(ESerializationVersion version)
+void GameConnection::setSerializationVersion(ESerializationVersion version)
 {
 	deserializer->version = version;
 	serializer->version = version;

+ 11 - 9
lib/serializer/Connection.h → lib/serializer/GameConnection.h

@@ -1,5 +1,5 @@
 /*
- * Connection.h, part of VCMI engine
+ * GameConnection.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -9,6 +9,8 @@
  */
 #pragma once
 
+#include "GameConnectionID.h"
+
 enum class ESerializationVersion : int32_t;
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -17,20 +19,20 @@ class BinaryDeserializer;
 class BinarySerializer;
 struct CPack;
 class INetworkConnection;
-class ConnectionPackReader;
-class ConnectionPackWriter;
+class GameConnectionPackReader;
+class GameConnectionPackWriter;
 class CGameState;
 class IGameInfoCallback;
 
 /// Wrapper class for game connection
 /// Handles serialization and deserialization of data received from network
-class DLL_LINKAGE CConnection : boost::noncopyable
+class DLL_LINKAGE GameConnection final : boost::noncopyable
 {
 	/// Non-owning pointer to underlying connection
 	std::weak_ptr<INetworkConnection> networkConnection;
 
-	std::unique_ptr<ConnectionPackReader> packReader;
-	std::unique_ptr<ConnectionPackWriter> packWriter;
+	std::unique_ptr<GameConnectionPackReader> packReader;
+	std::unique_ptr<GameConnectionPackWriter> packWriter;
 	std::unique_ptr<BinaryDeserializer> deserializer;
 	std::unique_ptr<BinarySerializer> serializer;
 
@@ -41,10 +43,10 @@ public:
 	std::shared_ptr<INetworkConnection> getConnection();
 
 	std::string uuid;
-	int connectionID;
+	GameConnectionID connectionID = GameConnectionID::INVALID;
 
-	explicit CConnection(std::weak_ptr<INetworkConnection> networkConnection);
-	~CConnection();
+	explicit GameConnection(std::weak_ptr<INetworkConnection> networkConnection);
+	~GameConnection();
 
 	void sendPack(const CPack & pack);
 	std::unique_ptr<CPack> retrievePack(const std::vector<std::byte> & data);

+ 20 - 0
lib/serializer/GameConnectionID.h

@@ -0,0 +1,20 @@
+/*
+ * GameConnectionID.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
+
+enum class GameConnectionID : int8_t
+{
+	INVALID = -1,
+	FIRST_CONNECTION = 1
+};
+
+VCMI_LIB_NAMESPACE_END

+ 21 - 0
lib/serializer/PlayerConnectionID.h

@@ -0,0 +1,21 @@
+/*
+ * PlayerConnectionID.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
+
+enum class PlayerConnectionID : int8_t
+{
+	INVALID = -1,
+	PLAYER_AI = 0,
+	FIRST_HUMAN = 1
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/serializer/RegisterTypes.h

@@ -148,7 +148,6 @@ void registerTypes(Serializer &s)
 	s.template registerType<ChangeObjPos>(101);
 	s.template registerType<PlayerEndsTurn>(102);
 	s.template registerType<PlayerEndsGame>(103);
-	s.template registerType<PlayerReinitInterface>(104);
 	s.template registerType<RemoveBonus>(105);
 	s.template registerType<ChangeFormation>(107);
 	s.template registerType<RemoveObject>(108);
@@ -291,6 +290,7 @@ void registerTypes(Serializer &s)
 	s.template registerType<GiveStackExperience>(248);
 	s.template registerType<TimesStackSizeUpdater>(249);
 	s.template registerType<TimesArmySizeUpdater>(250);
+	s.template registerType<PackageReceived>(251);
 }
 
 VCMI_LIB_NAMESPACE_END

+ 88 - 139
server/CGameHandler.cpp

@@ -75,7 +75,6 @@
 
 #include "../lib/serializer/CSaveFile.h"
 #include "../lib/serializer/CLoadFile.h"
-#include "../lib/serializer/Connection.h"
 
 #include "../lib/spells/CSpellHandler.h"
 
@@ -128,9 +127,9 @@ events::EventBus * CGameHandler::eventBus() const
 	return serverEventBus.get();
 }
 
-CVCMIServer & CGameHandler::gameLobby() const
+IGameServer & CGameHandler::gameServer() const
 {
-	return *lobby;
+	return server;
 }
 
 void CGameHandler::levelUpHero(const CGHeroInstance * hero, SecondarySkill skill)
@@ -437,58 +436,64 @@ void CGameHandler::changeSecSkill(const CGHeroInstance * hero, SecondarySkill wh
 
 }
 
-void CGameHandler::handleClientDisconnection(const std::shared_ptr<CConnection> & c)
+void CGameHandler::handleClientDisconnection(GameConnectionID connectionID)
 {
-	if(gameLobby().getState() == EServerState::SHUTDOWN || !gameState().getStartInfo())
+	if(gameServer().getState() == EServerState::SHUTDOWN || !gameState().getStartInfo())
 	{
 		assert(0); // game should have shut down before reaching this point!
 		return;
 	}
-	
-	for(auto & playerConnections : connections)
-	{
-		PlayerColor playerId = playerConnections.first;
 
-		auto playerConnection = vstd::find(playerConnections.second, c);
-		if(playerConnection == playerConnections.second.end())
+	std::vector<PlayerColor> disconnectedPlayers;
+	std::vector<PlayerColor> remainingPlayers;
+
+	// this player have left the game - broadcast infowindow to all in-game players
+	for (const auto & player : gameState().players)
+	{
+		if (gameInfo().getPlayerState(player.first)->status != EPlayerStatus::INGAME)
 			continue;
 
-		logGlobal->trace("Player %s disconnected. Notifying remaining players", playerId.toString());
+		if (gameServer().hasPlayerAt(player.first, connectionID))
+			disconnectedPlayers.push_back(player.first);
+		else
+			remainingPlayers.push_back(player.first);
+	}
 
-		// this player have left the game - broadcast infowindow to all in-game players
-		for (const auto & player : gameState().players)
+	for (const auto & inGamePlayer : remainingPlayers)
+	{
+		for (const auto & lostPlayer : disconnectedPlayers)
 		{
-			if (player.first == playerId)
-				continue;
-
-			if (gameInfo().getPlayerState(player.first)->status != EPlayerStatus::INGAME)
-				continue;
-
-			logGlobal->trace("Notifying player %s", player.first);
-
 			InfoWindow out;
-			out.player = player.first;
+			out.player = inGamePlayer;
 			out.text.appendTextID("vcmi.server.errors.playerLeft");
-			out.text.replaceName(playerId);
-			out.components.emplace_back(ComponentType::FLAG, playerId);
+			out.text.replaceName(lostPlayer);
+			out.components.emplace_back(ComponentType::FLAG, lostPlayer);
 			sendAndApply(out);
 		}
 	}
 }
 
-void CGameHandler::handleReceivedPack(std::shared_ptr<CConnection> connection, CPackForServer & pack)
+void CGameHandler::handleReceivedPack(GameConnectionID connection, CPackForServer & pack)
 {
 	//prepare struct informing that action was applied
 	auto sendPackageResponse = [&](bool successfullyApplied)
 	{
-		PackageApplied applied;
-		applied.player = pack.player;
-		applied.result = successfullyApplied;
-		applied.packType = CTypeList::getInstance().getTypeID(&pack);
-		applied.requestID = pack.requestID;
-		connection->sendPack(applied);
+		PackageApplied applied(
+			pack.player,
+			pack.requestID,
+			CTypeList::getInstance().getTypeID(&pack),
+			successfullyApplied
+		);
+		gameServer().sendPack(applied, connection);
 	};
 
+	PackageReceived received(
+		pack.player,
+		pack.requestID,
+		CTypeList::getInstance().getTypeID(&pack)
+	);
+	gameServer().sendPack(received, connection);
+
 	if(isBlockedByQueries(&pack, pack.player))
 	{
 		sendPackageResponse(false);
@@ -517,8 +522,15 @@ void CGameHandler::handleReceivedPack(std::shared_ptr<CConnection> connection, C
 	}
 }
 
-CGameHandler::CGameHandler(CVCMIServer * lobby)
-	: lobby(lobby)
+CGameHandler::CGameHandler(IGameServer & server, const std::shared_ptr<CGameState> & initialGamestate)
+	:CGameHandler(server)
+{
+	gs = initialGamestate;
+	randomizer = std::make_unique<GameRandomizer>(*gs);
+}
+
+CGameHandler::CGameHandler(IGameServer & server)
+	: server(server)
 	, heroPool(std::make_unique<HeroPoolProcessor>(this))
 	, battles(std::make_unique<BattleProcessor>(this))
 	, queries(std::make_unique<QueriesProcessor>())
@@ -732,19 +744,6 @@ void CGameHandler::start(bool resume)
 {
 	LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume);
 
-	for (const auto & cc : gameLobby().activeConnections)
-	{
-		auto players = gameLobby().getAllClientPlayers(cc->connectionID);
-		std::stringstream sbuffer;
-		sbuffer << "Connection " << cc->connectionID << " will handle " << players.size() << " player: ";
-		for (PlayerColor color : players)
-		{
-			sbuffer << color << " ";
-			connections[color].insert(cc);
-		}
-		logGlobal->info(sbuffer.str());
-	}
-
 #if SCRIPTING_ENABLED
 	services()->scripts()->run(serverScripts);
 #endif
@@ -1129,7 +1128,7 @@ void CGameHandler::showBlockingDialog(const IObjectInterface * caller, BlockingD
 	auto dialogQuery = std::make_shared<CBlockingDialogQuery>(this, caller, *iw);
 	queries->addQuery(dialogQuery);
 	iw->queryID = dialogQuery->queryID;
-	sendToAllClients(*iw);
+	sendAndApply(*iw);
 }
 
 void CGameHandler::showTeleportDialog(TeleportDialog *iw)
@@ -1137,7 +1136,7 @@ void CGameHandler::showTeleportDialog(TeleportDialog *iw)
 	auto dialogQuery = std::make_shared<CTeleportDialogQuery>(this, *iw);
 	queries->addQuery(dialogQuery);
 	iw->queryID = dialogQuery->queryID;
-	sendToAllClients(*iw);
+	sendAndApply(*iw);
 }
 
 void CGameHandler::giveResource(PlayerColor player, GameResID which, int val)
@@ -1502,18 +1501,9 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)
 	}
 }
 
-void CGameHandler::sendToAllClients(const CPackForClient & pack)
-{
-	logNetwork->trace("\tSending to all clients: %s", typeid(pack).name());
-	for (const auto & c : gameLobby().activeConnections)
-		c->sendPack(pack);
-}
-
 void CGameHandler::sendAndApply(CPackForClient & pack)
 {
-	sendToAllClients(pack);
-	gs->apply(pack);
-	logNetwork->trace("\tApplied on gameState(): %s", typeid(pack).name());
+	gameServer().applyPack(pack);
 }
 
 void CGameHandler::sendAndApply(CGarrisonOperationPack & pack)
@@ -1534,62 +1524,62 @@ void CGameHandler::sendAndApply(NewStructures & pack)
 	checkVictoryLossConditionsForPlayer(gameInfo().getTown(pack.tid)->tempOwner);
 }
 
-bool CGameHandler::isPlayerOwns(const std::shared_ptr<CConnection> & connection, const CPackForServer * pack, ObjectInstanceID id)
+bool CGameHandler::isPlayerOwns(GameConnectionID connectionID, const CPackForServer * pack, ObjectInstanceID id)
 {
-	return pack->player == gameState().getOwner(id) && hasPlayerAt(gameState().getOwner(id), connection);
+	return pack->player == gameState().getOwner(id) && hasPlayerAt(gameState().getOwner(id), connectionID);
 }
 
-void CGameHandler::throwNotAllowedAction(const std::shared_ptr<CConnection> & connection)
+void CGameHandler::throwNotAllowedAction(GameConnectionID connectionID)
 {
-	playerMessages->sendSystemMessage(connection, MetaString::createFromTextID("vcmi.server.errors.notAllowed"));
+	playerMessages->sendSystemMessage(connectionID, MetaString::createFromTextID("vcmi.server.errors.notAllowed"));
 
 	logNetwork->error("Player is not allowed to perform this action!");
 	throw ExceptionNotAllowedAction();
 }
 
-void CGameHandler::wrongPlayerMessage(const std::shared_ptr<CConnection> & connection, const CPackForServer * pack, PlayerColor expectedplayer)
+void CGameHandler::wrongPlayerMessage(GameConnectionID connectionID, const CPackForServer * pack, PlayerColor expectedplayer)
 {
 	auto str = MetaString::createFromTextID("vcmi.server.errors.wrongIdentified");
 	str.replaceName(pack->player);
 	str.replaceName(expectedplayer);
 	logNetwork->error(str.toString());
 
-	playerMessages->sendSystemMessage(connection, str);
+	playerMessages->sendSystemMessage(connectionID, str);
 }
 
-void CGameHandler::throwIfWrongOwner(const std::shared_ptr<CConnection> & connection, const CPackForServer * pack, ObjectInstanceID id)
+void CGameHandler::throwIfWrongOwner(GameConnectionID connectionID, const CPackForServer * pack, ObjectInstanceID id)
 {
-	if(!isPlayerOwns(connection, pack, id))
+	if(!isPlayerOwns(connectionID, pack, id))
 	{
-		wrongPlayerMessage(connection, pack, gameState().getOwner(id));
-		throwNotAllowedAction(connection);
+		wrongPlayerMessage(connectionID, pack, gameState().getOwner(id));
+		throwNotAllowedAction(connectionID);
 	}
 }
 
-void CGameHandler::throwIfPlayerNotActive(const std::shared_ptr<CConnection> & connection, const CPackForServer * pack)
+void CGameHandler::throwIfPlayerNotActive(GameConnectionID connectionID, const CPackForServer * pack)
 {
-	if (!turnOrder->isPlayerMakingTurn(pack->player))
-		throwNotAllowedAction(connection);
+	if (!vstd::contains(gs->actingPlayers, pack->player))
+		throwNotAllowedAction(connectionID);
 }
 
-void CGameHandler::throwIfWrongPlayer(const std::shared_ptr<CConnection> & connection, const CPackForServer * pack)
+void CGameHandler::throwIfWrongPlayer(GameConnectionID connectionID, const CPackForServer * pack)
 {
-	throwIfWrongPlayer(connection, pack, pack->player);
+	throwIfWrongPlayer(connectionID, pack, pack->player);
 }
 
-void CGameHandler::throwIfWrongPlayer(const std::shared_ptr<CConnection> & connection, const CPackForServer * pack, PlayerColor player)
+void CGameHandler::throwIfWrongPlayer(GameConnectionID connectionID, const CPackForServer * pack, PlayerColor player)
 {
-	if(!hasPlayerAt(player, connection) || pack->player != player)
+	if(!hasPlayerAt(player, connectionID) || pack->player != player)
 	{
-		wrongPlayerMessage(connection, pack, player);
-		throwNotAllowedAction(connection);
+		wrongPlayerMessage(connectionID, pack, player);
+		throwNotAllowedAction(connectionID);
 	}
 }
 
-void CGameHandler::throwAndComplain(const std::shared_ptr<CConnection> & connection, const std::string & txt)
+void CGameHandler::throwAndComplain(GameConnectionID connectionID, const std::string & txt)
 {
 	complain(txt);
-	throwNotAllowedAction(connection);
+	throwNotAllowedAction(connectionID);
 }
 
 void CGameHandler::save(const std::string & filename)
@@ -1614,64 +1604,23 @@ void CGameHandler::save(const std::string & filename)
 	}
 }
 
-bool CGameHandler::load(const std::string & filename)
+void CGameHandler::load(const StartInfo &info)
 {
-	logGlobal->info("Loading from %s", filename);
-	const auto stem	= FileInfo::GetPathStem(filename);
+	logGlobal->info("Loading from %s", info.fileURI);
+	const auto stem	= FileInfo::GetPathStem(info.fileURI);
 
 	reinitScripting();
 
-	try
-	{
-		CLoadFile lf(*CResourceHandler::get()->getResourceName(ResourcePath(stem.to_string(), EResType::SAVEGAME)), gs.get());
-		gs = std::make_shared<CGameState>();
-		randomizer = std::make_unique<GameRandomizer>(*gs);
-		gs->loadGame(lf);
-		logGlobal->info("Loading server state");
-		lf.load(*this);
-		logGlobal->info("Game has been successfully loaded!");
-	}
-	catch(const ModIncompatibility & e)
-	{
-		logGlobal->error("Failed to load game: %s", e.what());
-		MetaString errorMsg;
-		if(!e.whatMissing().empty())
-		{
-			errorMsg.appendTextID("vcmi.server.errors.modsToEnable");
-			errorMsg.appendRawString("\n");
-			errorMsg.appendRawString(e.whatMissing());
-		}
-		if(!e.whatExcessive().empty())
-		{
-			errorMsg.appendTextID("vcmi.server.errors.modsToDisable");
-			errorMsg.appendRawString("\n");
-			errorMsg.appendRawString(e.whatExcessive());
-		}
-		gameLobby().announceMessage(errorMsg);
-		return false;
-	}
-	catch(const IdentifierResolutionException & e)
-	{
-		logGlobal->error("Failed to load game: %s", e.what());
-		MetaString errorMsg;
-		errorMsg.appendTextID("vcmi.server.errors.unknownEntity");
-		errorMsg.replaceRawString(e.identifierName);
-		gameLobby().announceMessage(errorMsg);
-		return false;
-	}
+	CLoadFile lf(*CResourceHandler::get()->getResourceName(ResourcePath(stem.to_string(), EResType::SAVEGAME)), gs.get());
+	gs = std::make_shared<CGameState>();
+	randomizer = std::make_unique<GameRandomizer>(*gs);
+	gs->loadGame(lf);
+	logGlobal->info("Loading server state");
+	lf.load(*this);
+	logGlobal->info("Game has been successfully loaded!");
 
-	catch(const std::exception & e)
-	{
-		logGlobal->error("Failed to load game: %s", e.what());
-		auto str = MetaString::createFromTextID("vcmi.broadcast.failedLoadGame");
-		str.appendRawString(": ");
-		str.appendRawString(e.what());
-		gameLobby().announceMessage(str);
-		return false;
-	}
 	gs->preInit(LIBRARY);
-	gs->updateOnLoad(gameLobby().si.get());
-	return true;
+	gs->updateOnLoad(info);
 }
 
 bool CGameHandler::bulkSplitStack(SlotID slotSrc, ObjectInstanceID srcOwner, si32 howMany)
@@ -2084,14 +2033,14 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8
 	return true;
 }
 
-bool CGameHandler::hasPlayerAt(PlayerColor player,  const std::shared_ptr<CConnection> & c) const
+bool CGameHandler::hasPlayerAt(PlayerColor player,  GameConnectionID connectionID) const
 {
-	return connections.count(player) && connections.at(player).count(c);
+	return gameServer().hasPlayerAt(player, connectionID);
 }
 
 bool CGameHandler::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const
 {
-	return connections.count(left) && connections.count(right) && connections.at(left) == connections.at(right);
+	return gameServer().hasBothPlayersAtSameConnection(left, right);
 }
 
 bool CGameHandler::disbandCreature(ObjectInstanceID id, SlotID pos)
@@ -3578,7 +3527,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
 
 			if(p->human)
 			{
-				gameLobby().setState(EServerState::SHUTDOWN);
+				gameServer().setState(EServerState::SHUTDOWN);
 			}
 		}
 		else
@@ -4118,7 +4067,7 @@ void CGameHandler::removeAfterVisit(const ObjectInstanceID & id)
 
 void CGameHandler::changeFogOfWar(int3 center, ui32 radius, PlayerColor player, ETileVisibility mode)
 {
-	std::unordered_set<int3> tiles;
+	FowTilesType tiles;
 
 	if (mode == ETileVisibility::HIDDEN)
 	{
@@ -4131,7 +4080,7 @@ void CGameHandler::changeFogOfWar(int3 center, ui32 radius, PlayerColor player,
 	changeFogOfWar(tiles, player, mode);
 }
 
-void CGameHandler::changeFogOfWar(const std::unordered_set<int3> &tiles, PlayerColor player, ETileVisibility mode)
+void CGameHandler::changeFogOfWar(const FowTilesType &tiles, PlayerColor player, ETileVisibility mode)
 {
 	if (tiles.empty())
 		return;
@@ -4144,7 +4093,7 @@ void CGameHandler::changeFogOfWar(const std::unordered_set<int3> &tiles, PlayerC
 	if (mode == ETileVisibility::HIDDEN)
 	{
 		// do not hide tiles observed by owned objects. May lead to disastrous AI problems
-		std::unordered_set<int3> observedTiles;
+		FowTilesType observedTiles;
 		const auto * p = gameInfo().getPlayerState(player);
 		for (const auto * obj : p->getOwnedObjects())
 			gameInfo().getTilesInRange(observedTiles, obj->getSightCenter(), obj->getSightRadius(), ETileVisibility::REVEALED, obj->getOwner());

+ 19 - 20
server/CGameHandler.h

@@ -16,13 +16,13 @@
 #include "../lib/ScriptHandler.h"
 #include "../lib/gameState/GameStatistics.h"
 #include "../lib/networkPacks/PacksForServer.h"
+#include "../lib/serializer/GameConnectionID.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 struct SideInBattle;
 class IMarket;
 class SpellCastEnvironment;
-class CConnection;
 class CCommanderInstance;
 class EVictoryLossCheckResult;
 class CRandomGenerator;
@@ -57,10 +57,11 @@ class TurnTimerHandler;
 class QueriesProcessor;
 class CObjectVisitQuery;
 class NewTurnProcessor;
+class IGameServer;
 
 class CGameHandler : public Environment, public IGameEventCallback
 {
-	CVCMIServer * lobby;
+	IGameServer & server;
 
 public:
 	std::unique_ptr<HeroPoolProcessor> heroPool;
@@ -81,8 +82,6 @@ public:
 
 	std::unique_ptr<PlayerMessageProcessor> playerMessages;
 
-	std::map<PlayerColor, std::set<std::shared_ptr<CConnection>>> connections; //player color -> connection to client with interface of that player
-
 	//queries stuff
 	QueryID QID;
 
@@ -92,7 +91,7 @@ public:
 	const GameCb * game() const override;
 	vstd::CLoggerBase * logger() const override;
 	events::EventBus * eventBus() const override;
-	CVCMIServer & gameLobby() const;
+	IGameServer & gameServer() const;
 	ServerCallback * spellcastEnvironment() const;
 
 	bool isBlockedByQueries(const CPackForServer *pack, PlayerColor player);
@@ -110,7 +109,8 @@ public:
 	void createHole(const int3 & visitablePosition, PlayerColor initiator);
 	void newObject(std::shared_ptr<CGObjectInstance> object, PlayerColor initiator);
 
-	explicit CGameHandler(CVCMIServer * lobby);
+	explicit CGameHandler(IGameServer & server);
+	CGameHandler(IGameServer & server, const std::shared_ptr<CGameState> & gamestate);
 	~CGameHandler();
 
 	//////////////////////////////////////////////////////////////////////////
@@ -173,7 +173,7 @@ public:
 	void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override;
 
 	void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, ETileVisibility mode) override;
-	void changeFogOfWar(const std::unordered_set<int3> &tiles, PlayerColor player,ETileVisibility mode) override;
+	void changeFogOfWar(const FowTilesType &tiles, PlayerColor player,ETileVisibility mode) override;
 	
 	void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override;
 	void useChargeBasedSpell(const ObjectInstanceID & heroObjectID, const SpellID & spellID);
@@ -204,9 +204,9 @@ public:
 	//////////////////////////////////////////////////////////////////////////
 
 	void init(StartInfo *si, Load::ProgressAccumulator & progressTracking);
-	void handleClientDisconnection(const std::shared_ptr<CConnection> & c);
-	void handleReceivedPack(std::shared_ptr<CConnection> c, CPackForServer & pack);
-	bool hasPlayerAt(PlayerColor player, const std::shared_ptr<CConnection> & c) const;
+	void handleClientDisconnection(GameConnectionID connectionI);
+	void handleReceivedPack(GameConnectionID connectionId, CPackForServer & pack);
+	bool hasPlayerAt(PlayerColor player, GameConnectionID connectionId) const;
 	bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const;
 
 	bool queryReply( QueryID qid, std::optional<int32_t> reply, PlayerColor player );
@@ -237,7 +237,7 @@ public:
 	bool bulkMergeStacks(SlotID slotSrc, ObjectInstanceID srcOwner);
 	bool bulkSplitAndRebalanceStack(SlotID slotSrc, ObjectInstanceID srcOwner);
 	void save(const std::string &fname);
-	bool load(const std::string &fname);
+	void load(const StartInfo &info);
 
 	void onPlayerTurnStarted(PlayerColor which);
 	void onPlayerTurnEnded(PlayerColor which);
@@ -276,25 +276,24 @@ public:
 #endif
 	}
 
-	void sendToAllClients(const CPackForClient & pack);
 	void sendAndApply(CPackForClient & pack) override;
 	void sendAndApply(CGarrisonOperationPack & pack);
 	void sendAndApply(SetResources & pack);
 	void sendAndApply(NewStructures & pack);
 
-	void wrongPlayerMessage(const std::shared_ptr<CConnection> & connection, const CPackForServer * pack, PlayerColor expectedplayer);
+	void wrongPlayerMessage(GameConnectionID connectionID, const CPackForServer * pack, PlayerColor expectedplayer);
 	/// Unconditionally throws with "Action not allowed" message
-	[[noreturn]] void throwNotAllowedAction(const std::shared_ptr<CConnection> & connection);
+	[[noreturn]] void throwNotAllowedAction(GameConnectionID connectionID);
 	/// Throws if player stated in pack is not making turn right now
-	void throwIfPlayerNotActive(const std::shared_ptr<CConnection> & connection, const CPackForServer * pack);
+	void throwIfPlayerNotActive(GameConnectionID connectionID, const CPackForServer * pack);
 	/// Throws if object is not owned by pack sender
-	void throwIfWrongOwner(const std::shared_ptr<CConnection> & connection, const CPackForServer * pack, ObjectInstanceID id);
+	void throwIfWrongOwner(GameConnectionID connectionID, const CPackForServer * pack, ObjectInstanceID id);
 	/// Throws if player is not present on connection of this pack
-	void throwIfWrongPlayer(const std::shared_ptr<CConnection> & connection, const CPackForServer * pack, PlayerColor player);
-	void throwIfWrongPlayer(const std::shared_ptr<CConnection> & connection, const CPackForServer * pack);
-	[[noreturn]] void throwAndComplain(const std::shared_ptr<CConnection> & connection, const std::string & txt);
+	void throwIfWrongPlayer(GameConnectionID connectionID, const CPackForServer * pack, PlayerColor player);
+	void throwIfWrongPlayer(GameConnectionID connectionID, const CPackForServer * pack);
+	[[noreturn]] void throwAndComplain(GameConnectionID connectionID, const std::string & txt);
 
-	bool isPlayerOwns(const std::shared_ptr<CConnection> & connection, const CPackForServer * pack, ObjectInstanceID id);
+	bool isPlayerOwns(GameConnectionID connectionID, const CPackForServer * pack, ObjectInstanceID id);
 
 	void start(bool resume);
 	void tick(int millisecondsPassed);

+ 1 - 0
server/CMakeLists.txt

@@ -47,6 +47,7 @@ set(vcmiservercommon_HEADERS
 
 		CGameHandler.h
 		GlobalLobbyProcessor.h
+		IGameServer.h
 		ServerSpellCastEnvironment.h
 		CVCMIServer.h
 		LobbyNetPackVisitors.h

+ 139 - 72
server/CVCMIServer.cpp

@@ -23,9 +23,10 @@
 #include "../lib/gameState/CGameState.h"
 #include "../lib/mapping/CMapInfo.h"
 #include "../lib/mapping/CMapHeader.h"
+#include "../lib/modding/ModIncompatibility.h"
 #include "../lib/rmg/CMapGenOptions.h"
 #include "../lib/serializer/CMemorySerializer.h"
-#include "../lib/serializer/Connection.h"
+#include "../lib/serializer/GameConnection.h"
 #include "../lib/texts/CGeneralTextHandler.h"
 
 // UUID generation
@@ -39,10 +40,10 @@ class CVCMIServerPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
 private:
 	CVCMIServer & handler;
 	std::shared_ptr<CGameHandler> gh;
-	std::shared_ptr<CConnection> connection;
+	std::shared_ptr<GameConnection> connection;
 
 public:
-	CVCMIServerPackVisitor(CVCMIServer & handler, const std::shared_ptr<CGameHandler> & gh, const std::shared_ptr<CConnection> & connection)
+	CVCMIServerPackVisitor(CVCMIServer & handler, const std::shared_ptr<CGameHandler> & gh, const std::shared_ptr<GameConnection> & connection)
 		: handler(handler)
 		, gh(gh)
 		, connection(connection)
@@ -59,7 +60,7 @@ public:
 	void visitForServer(CPackForServer & serverPack) override
 	{
 		if (gh)
-			gh->handleReceivedPack(connection, serverPack);
+			gh->handleReceivedPack(connection->connectionID, serverPack);
 		else
 			logNetwork->error("Received pack for game server while in lobby!");
 	}
@@ -70,8 +71,8 @@ public:
 };
 
 CVCMIServer::CVCMIServer(uint16_t port, bool runByClient)
-	: currentClientId(1)
-	, currentPlayerId(1)
+	: currentClientId(GameConnectionID::FIRST_CONNECTION)
+	, currentPlayerId(PlayerConnectionID::FIRST_HUMAN)
 	, port(port)
 	, runByClient(runByClient)
 {
@@ -116,7 +117,7 @@ void CVCMIServer::onNewConnection(const std::shared_ptr<INetworkConnection> & co
 {
 	if(getState() == EServerState::LOBBY)
 	{
-		activeConnections.push_back(std::make_shared<CConnection>(connection));
+		activeConnections.push_back(std::make_shared<GameConnection>(connection));
 		activeConnections.back()->enterLobbyConnectionMode();
 	}
 	else
@@ -128,7 +129,7 @@ void CVCMIServer::onNewConnection(const std::shared_ptr<INetworkConnection> & co
 
 void CVCMIServer::onPacketReceived(const std::shared_ptr<INetworkConnection> & connection, const std::vector<std::byte> & message)
 {
-	std::shared_ptr<CConnection> c = findConnection(connection);
+	std::shared_ptr<GameConnection> c = findConnection(connection);
 	if (c == nullptr)
 		throw std::out_of_range("Unknown connection received in CVCMIServer::findConnection");
 
@@ -155,7 +156,7 @@ EServerState CVCMIServer::getState() const
 	return state;
 }
 
-std::shared_ptr<CConnection> CVCMIServer::findConnection(const std::shared_ptr<INetworkConnection> & netConnection)
+std::shared_ptr<GameConnection> CVCMIServer::findConnection(const std::shared_ptr<INetworkConnection> & netConnection)
 {
 	for(const auto & gameConnection : activeConnections)
 	{
@@ -250,7 +251,7 @@ bool CVCMIServer::prepareToStartGame()
 		}
 	});
 
-	gh = std::make_shared<CGameHandler>(this);
+	gh = std::make_shared<CGameHandler>(*this);
 	switch(si->mode)
 	{
 	case EStartMode::CAMPAIGN:
@@ -271,7 +272,7 @@ bool CVCMIServer::prepareToStartGame()
 
 	case EStartMode::LOAD_GAME:
 		logNetwork->info("Preparing to start loaded game");
-		if(!gh->load(si->mapname))
+		if(!loadSavedGame(*si))
 		{
 			current.finish();
 			progressTrackingThread.join();
@@ -295,6 +296,17 @@ void CVCMIServer::startGameImmediately()
 	for(auto activeConnection : activeConnections)
 		activeConnection->setCallback(gh->gameInfo());
 
+	for(auto activeConnection : activeConnections)
+	{
+		auto players = getAllClientPlayers(activeConnection->connectionID);
+		std::stringstream sbuffer;
+		sbuffer << "Connection " << static_cast<int>(activeConnection->connectionID) << " will handle " << players.size() << " player: ";
+		for (PlayerColor color : players)
+			sbuffer << color << " ";
+
+		logGlobal->info(sbuffer.str());
+	}
+
 	gh->start(si->mode == EStartMode::LOAD_GAME);
 	setState(EServerState::GAMEPLAY);
 	lastTimerUpdateTime = gameplayStartTime = std::chrono::steady_clock::now();
@@ -307,7 +319,7 @@ void CVCMIServer::onDisconnected(const std::shared_ptr<INetworkConnection> & con
 {
 	logNetwork->error("Network error receiving a pack. Connection has been closed");
 
-	std::shared_ptr<CConnection> c = findConnection(connection);
+	std::shared_ptr<GameConnection> c = findConnection(connection);
 
 	// player may have already disconnected via clientDisconnected call
 	if (c)
@@ -318,7 +330,7 @@ void CVCMIServer::onDisconnected(const std::shared_ptr<INetworkConnection> & con
 	}
 }
 
-void CVCMIServer::handleReceivedPack(std::shared_ptr<CConnection> connection, CPackForLobby & pack)
+void CVCMIServer::handleReceivedPack(std::shared_ptr<GameConnection> connection, CPackForLobby & pack)
 {
 	ClientPermissionsCheckerNetPackVisitor checker(*this, connection);
 	pack.visit(checker);
@@ -378,7 +390,7 @@ void CVCMIServer::announceTxt(const std::string & txt, const std::string & playe
 	announceTxt(str, playerName);
 }
 
-bool CVCMIServer::passHost(int toConnectionId)
+bool CVCMIServer::passHost(GameConnectionID toConnectionId)
 {
 	for(auto activeConnection : activeConnections)
 	{
@@ -388,37 +400,37 @@ bool CVCMIServer::passHost(int toConnectionId)
 			continue;
 
 		hostClientId = activeConnection->connectionID;
-		announceTxt(boost::str(boost::format("Pass host to connection %d") % toConnectionId));
+		announceTxt(boost::str(boost::format("Pass host to connection %d") % static_cast<int>(toConnectionId)));
 		return true;
 	}
 	return false;
 }
 
-void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<std::string> & names, const std::string & uuid, EStartMode mode)
+void CVCMIServer::clientConnected(std::shared_ptr<GameConnection> c, std::vector<std::string> & names, const std::string & uuid, EStartMode mode)
 {
 	assert(getState() == EServerState::LOBBY);
 
-	c->connectionID = currentClientId++;
+	c->connectionID = vstd::next(currentClientId, 1);
 	c->uuid = uuid;
 
-	if(hostClientId == -1)
+	if(hostClientId == GameConnectionID::INVALID)
 	{
 		hostClientId = c->connectionID;
 		si->mode = mode;
 	}
 
-	logNetwork->info("Connection with client %d established. UUID: %s", c->connectionID, c->uuid);
+	logNetwork->info("Connection with client %d established. UUID: %s", static_cast<int>(c->connectionID), c->uuid);
 
 	for(auto & name : names)
 	{
-		logNetwork->info("Client %d player: %s", c->connectionID, name);
-		ui8 id = currentPlayerId++;
+		logNetwork->info("Client %d player: %s", static_cast<int>(c->connectionID), name);
+		PlayerConnectionID id = vstd::next(currentPlayerId, 1);
 
 		ClientPlayer cp;
 		cp.connection = c->connectionID;
 		cp.name = name;
 		playerNames.try_emplace(id, cp);
-		announceTxt(boost::str(boost::format("%s (pid %d cid %d) joins the game") % name % id % c->connectionID));
+		announceTxt(boost::str(boost::format("%s (pid %d cid %d) joins the game") % name % static_cast<int>(id) % static_cast<int>(c->connectionID)));
 
 		//put new player in first slot with AI
 		for(auto & elem : si->playerInfos)
@@ -432,7 +444,7 @@ void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<st
 	}
 }
 
-void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> connection)
+void CVCMIServer::clientDisconnected(std::shared_ptr<GameConnection> connection)
 {
 	assert(vstd::contains(activeConnections, connection));
 	logGlobal->trace("Received disconnection request");
@@ -446,39 +458,11 @@ void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> connection)
 
 	if(gh && getState() == EServerState::GAMEPLAY)
 	{
-		gh->handleClientDisconnection(connection);
+		gh->handleClientDisconnection(connection->connectionID);
 	}
 }
 
-void CVCMIServer::reconnectPlayer(int connId)
-{
-	PlayerReinitInterface startAiPack;
-	startAiPack.playerConnectionId = connId;
-
-	if(gh && si && getState() == EServerState::GAMEPLAY)
-	{
-		for(auto it = playerNames.begin(); it != playerNames.end(); ++it)
-		{
-			if(it->second.connection != connId)
-				continue;
-
-			int id = it->first;
-			auto * playerSettings = si->getPlayersSettings(id);
-			if(!playerSettings)
-				continue;
-
-			std::string messageText = boost::str(boost::format("%s (cid %d) is connected") % playerSettings->name % connId);
-			gh->playerMessages->broadcastMessage(playerSettings->color, messageText);
-
-			startAiPack.players.push_back(playerSettings->color);
-		}
-
-		if(!startAiPack.players.empty())
-			gh->sendAndApply(startAiPack);
-	}
-}
-
-void CVCMIServer::setPlayerConnectedId(PlayerSettings & pset, ui8 player) const
+void CVCMIServer::setPlayerConnectedId(PlayerSettings & pset, PlayerConnectionID player) const
 {
 	if(vstd::contains(playerNames, player))
 		pset.name = playerNames.find(player)->second.name;
@@ -486,7 +470,7 @@ void CVCMIServer::setPlayerConnectedId(PlayerSettings & pset, ui8 player) const
 		pset.name = LIBRARY->generaltexth->allTexts[468]; //Computer
 
 	pset.connectedPlayerIDs.clear();
-	if(player != PlayerSettings::PLAYER_AI)
+	if(player != PlayerConnectionID::PLAYER_AI)
 		pset.connectedPlayerIDs.insert(player);
 }
 
@@ -513,7 +497,7 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo,
 			}
 			else
 			{
-				setPlayerConnectedId(ps.second, PlayerSettings::PLAYER_AI);
+				setPlayerConnectedId(ps.second, PlayerConnectionID::PLAYER_AI);
 			}
 		}
 	}
@@ -538,7 +522,7 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo,
 			}
 			else
 			{
-				setPlayerConnectedId(pset, PlayerSettings::PLAYER_AI);
+				setPlayerConnectedId(pset, PlayerConnectionID::PLAYER_AI);
 				if(!pinfo.canHumanPlay)
 				{
 					pset.compOnly = true;
@@ -614,8 +598,8 @@ void CVCMIServer::setPlayer(PlayerColor clickedColor)
 	struct PlayerToRestore
 	{
 		PlayerColor color;
-		int id;
-		void reset() { id = -1; color = PlayerColor::CANNOT_DETERMINE; }
+		PlayerConnectionID id;
+		void reset() { id = PlayerConnectionID::PLAYER_AI; color = PlayerColor::CANNOT_DETERMINE; }
 		PlayerToRestore(){ reset(); }
 	};
 
@@ -623,24 +607,24 @@ void CVCMIServer::setPlayer(PlayerColor clickedColor)
 	PlayerSettings & clicked = si->playerInfos[clickedColor];
 
 	//identify clicked player
-	int clickedNameID = 0; //number of player - zero means AI, assume it initially
+	PlayerConnectionID clickedNameID = PlayerConnectionID::PLAYER_AI;
 	if(clicked.isControlledByHuman())
 		clickedNameID = *(clicked.connectedPlayerIDs.begin()); //if not AI - set appropriate ID
 
-	if(clickedNameID > 0 && playerToRestore.id == clickedNameID) //player to restore is about to being replaced -> put him back to the old place
+	if(clickedNameID > PlayerConnectionID::PLAYER_AI && playerToRestore.id == clickedNameID) //player to restore is about to being replaced -> put him back to the old place
 	{
 		PlayerSettings & restPos = si->playerInfos[playerToRestore.color];
 		setPlayerConnectedId(restPos, playerToRestore.id);
 		playerToRestore.reset();
 	}
 
-	int newPlayer; //which player will take clicked position
+	PlayerConnectionID newPlayer; //which player will take clicked position
 
 	//who will be put here?
-	if(!clickedNameID) //AI player clicked -> if possible replace computer with unallocated player
+	if(clickedNameID == PlayerConnectionID::PLAYER_AI) //AI player clicked -> if possible replace computer with unallocated player
 	{
 		newPlayer = getIdOfFirstUnallocatedPlayer();
-		if(!newPlayer) //no "free" player -> get just first one
+		if(newPlayer == PlayerConnectionID::PLAYER_AI) //no "free" player -> get just first one
 			newPlayer = playerNames.begin()->first;
 	}
 	else //human clicked -> take next
@@ -651,23 +635,23 @@ void CVCMIServer::setPlayer(PlayerColor clickedColor)
 		if(i != playerNames.end())
 			newPlayer = i->first;
 		else
-			newPlayer = 0; //AI if we scrolled through all players
+			newPlayer = PlayerConnectionID::PLAYER_AI; //AI if we scrolled through all players
 	}
 
 	setPlayerConnectedId(clicked, newPlayer); //put player
 
 	//if that player was somewhere else, we need to replace him with computer
-	if(newPlayer) //not AI
+	if(newPlayer != PlayerConnectionID::PLAYER_AI) //not AI
 	{
 		for(auto i = si->playerInfos.begin(); i != si->playerInfos.end(); i++)
 		{
-			int curNameID = *(i->second.connectedPlayerIDs.begin());
+			PlayerConnectionID curNameID = *(i->second.connectedPlayerIDs.begin());
 			if(i->first != clickedColor && curNameID == newPlayer)
 			{
 				assert(i->second.connectedPlayerIDs.size());
 				playerToRestore.color = i->first;
 				playerToRestore.id = newPlayer;
-				setPlayerConnectedId(i->second, PlayerSettings::PLAYER_AI); //set computer
+				setPlayerConnectedId(i->second, PlayerConnectionID::PLAYER_AI); //set computer
 				break;
 			}
 		}
@@ -687,7 +671,7 @@ void CVCMIServer::setPlayerName(PlayerColor color, const std::string & name)
 	if(player.connectedPlayerIDs.empty())
 		return;
 
-	int nameID = *(player.connectedPlayerIDs.begin()); //if not AI - set appropriate ID
+	PlayerConnectionID nameID = *(player.connectedPlayerIDs.begin()); //if not AI - set appropriate ID
 
 	playerNames[nameID].name = name;
 	setPlayerConnectedId(player, nameID);
@@ -843,9 +827,9 @@ void CVCMIServer::setCampaignBonus(int bonusId)
 		for(auto & elem : si->playerInfos)
 		{
 			if(elem.first == startingPlayer)
-				setPlayerConnectedId(elem.second, 1);
+				setPlayerConnectedId(elem.second, PlayerConnectionID::FIRST_HUMAN);
 			else
-				setPlayerConnectedId(elem.second, PlayerSettings::PLAYER_AI);
+				setPlayerConnectedId(elem.second, PlayerConnectionID::PLAYER_AI);
 		}
 	}
 }
@@ -991,14 +975,14 @@ std::vector<HeroTypeID> CVCMIServer::getUsedHeroes()
 	return heroIds;
 }
 
-ui8 CVCMIServer::getIdOfFirstUnallocatedPlayer() const
+PlayerConnectionID CVCMIServer::getIdOfFirstUnallocatedPlayer() const
 {
 	for(auto i = playerNames.cbegin(); i != playerNames.cend(); i++)
 	{
 		if(!si->getPlayersSettings(i->first))
 			return i->first;
 	}
-	return 0;
+	return PlayerConnectionID::PLAYER_AI;
 }
 
 void CVCMIServer::multiplayerWelcomeMessage()
@@ -1077,3 +1061,86 @@ INetworkServer & CVCMIServer::getNetworkServer()
 {
 	return *networkServer;
 }
+
+bool CVCMIServer::loadSavedGame(const StartInfo &info)
+{
+	try
+	{
+		gh->load(info);
+	}
+	catch(const ModIncompatibility & e)
+	{
+		logGlobal->error("Failed to load game: %s", e.what());
+		MetaString errorMsg;
+		if(!e.whatMissing().empty())
+		{
+			errorMsg.appendTextID("vcmi.server.errors.modsToEnable");
+			errorMsg.appendRawString("\n");
+			errorMsg.appendRawString(e.whatMissing());
+		}
+		if(!e.whatExcessive().empty())
+		{
+			errorMsg.appendTextID("vcmi.server.errors.modsToDisable");
+			errorMsg.appendRawString("\n");
+			errorMsg.appendRawString(e.whatExcessive());
+		}
+		announceMessage(errorMsg);
+		return false;
+	}
+	catch(const IdentifierResolutionException & e)
+	{
+		logGlobal->error("Failed to load game: %s", e.what());
+		MetaString errorMsg;
+		errorMsg.appendTextID("vcmi.server.errors.unknownEntity");
+		errorMsg.replaceRawString(e.identifierName);
+		announceMessage(errorMsg);
+		return false;
+	}
+
+	catch(const std::exception & e)
+	{
+		logGlobal->error("Failed to load game: %s", e.what());
+		auto str = MetaString::createFromTextID("vcmi.broadcast.failedLoadGame");
+		str.appendRawString(": ");
+		str.appendRawString(e.what());
+		announceMessage(str);
+		return false;
+	}
+	return true;
+}
+
+bool CVCMIServer::isPlayerHost(const PlayerColor & color) const
+{
+	return LobbyInfo::isPlayerHost(color);
+}
+
+bool CVCMIServer::hasPlayerAt(PlayerColor player, GameConnectionID connectionID) const
+{
+	return vstd::contains(getAllClientPlayers(connectionID), player);
+}
+
+bool CVCMIServer::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const
+{
+	for (const auto & c : activeConnections)
+		if (hasPlayerAt(left, c->connectionID) && hasPlayerAt(right, c->connectionID))
+			return true;
+
+	return false;
+}
+
+void CVCMIServer::applyPack(CPackForClient & pack)
+{
+	logNetwork->trace("\tSending to all clients: %s", typeid(pack).name());
+	for (const auto & c : activeConnections)
+		c->sendPack(pack);
+	gh->gs->apply(pack);
+	logNetwork->trace("\tApplied on gameState(): %s", typeid(pack).name());
+}
+
+void CVCMIServer::sendPack(CPackForClient & pack, GameConnectionID connectionID)
+{
+	for (const auto & c : activeConnections)
+		if (c->connectionID == connectionID)
+			c->sendPack(pack);
+}
+

+ 27 - 24
server/CVCMIServer.h

@@ -9,6 +9,8 @@
  */
 #pragma once
 
+#include "IGameServer.h"
+
 #include "../lib/network/NetworkInterface.h"
 #include "../lib/StartInfo.h"
 
@@ -18,7 +20,7 @@ class CMapInfo;
 
 struct CPackForLobby;
 
-class CConnection;
+class GameConnection;
 struct StartInfo;
 struct LobbyInfo;
 struct PlayerSettings;
@@ -32,14 +34,7 @@ class CBaseForServerApply;
 class CBaseForGHApply;
 class GlobalLobbyProcessor;
 
-enum class EServerState : ui8
-{
-	LOBBY,
-	GAMEPLAY,
-	SHUTDOWN
-};
-
-class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INetworkTimerListener
+class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INetworkTimerListener, public IGameServer
 {
 	/// Network server instance that receives and processes incoming connections on active socket
 	std::unique_ptr<INetworkServer> networkServer;
@@ -52,16 +47,28 @@ class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INet
 
 	EServerState state = EServerState::LOBBY;
 
-	std::shared_ptr<CConnection> findConnection(const std::shared_ptr<INetworkConnection> &);
+	std::shared_ptr<GameConnection> findConnection(const std::shared_ptr<INetworkConnection> &);
 
-	int currentClientId;
-	ui8 currentPlayerId;
+	GameConnectionID currentClientId;
+	PlayerConnectionID currentPlayerId;
 	uint16_t port;
 	bool runByClient;
 
+
+	bool loadSavedGame(const StartInfo &info);
 public:
+
+	// IGameServer impl
+	void setState(EServerState value) override;
+	EServerState getState() const override;
+	bool isPlayerHost(const PlayerColor & color) const override;
+	bool hasPlayerAt(PlayerColor player, GameConnectionID connectionID) const override;
+	bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const override;
+	void applyPack(CPackForClient & pack) override;
+	void sendPack(CPackForClient & pack, GameConnectionID connectionID) override;
+
 	/// List of all active connections
-	std::vector<std::shared_ptr<CConnection>> activeConnections;
+	std::vector<std::shared_ptr<GameConnection>> activeConnections;
 
 	uint16_t prepare(bool connectToLobby, bool listenForConnections);
 
@@ -84,34 +91,30 @@ public:
 	void startGameImmediately();
 	uint16_t startAcceptingIncomingConnections(bool listenForConnections);
 
-	void threadHandleClient(std::shared_ptr<CConnection> c);
+	void threadHandleClient(std::shared_ptr<GameConnection> c);
 
 	void announcePack(CPackForLobby & pack);
-	bool passHost(int toConnectionId);
+	bool passHost(GameConnectionID toConnectionId);
 
 	void announceTxt(const MetaString & txt, const std::string & playerName = "system");
 	void announceTxt(const std::string & txt, const std::string & playerName = "system");
 
-	void setPlayerConnectedId(PlayerSettings & pset, ui8 player) const;
+	void setPlayerConnectedId(PlayerSettings & pset, PlayerConnectionID player) const;
 	void updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo, std::shared_ptr<CMapGenOptions> mapGenOpt = {});
 
-	void clientConnected(std::shared_ptr<CConnection> c, std::vector<std::string> & names, const std::string & uuid, EStartMode mode);
-	void clientDisconnected(std::shared_ptr<CConnection> c);
-	void reconnectPlayer(int connId);
+	void clientConnected(std::shared_ptr<GameConnection> c, std::vector<std::string> & names, const std::string & uuid, EStartMode mode);
+	void clientDisconnected(std::shared_ptr<GameConnection> c);
 
 	void announceMessage(const MetaString & txt);
 	void announceMessage(const std::string & txt);
 
-	void handleReceivedPack(std::shared_ptr<CConnection> connection, CPackForLobby & pack);
+	void handleReceivedPack(std::shared_ptr<GameConnection> connection, CPackForLobby & pack);
 
 	void updateAndPropagateLobbyState();
 
 	INetworkHandler & getNetworkHandler();
 	INetworkServer & getNetworkServer();
 
-	void setState(EServerState value);
-	EServerState getState() const;
-
 	// Work with LobbyInfo
 	void setPlayer(PlayerColor clickedColor);
 	void setPlayerName(PlayerColor player, const std::string & name);
@@ -130,7 +133,7 @@ public:
 	void setCampaignMap(CampaignScenarioID mapId);
 	void setCampaignBonus(int bonusId);
 
-	ui8 getIdOfFirstUnallocatedPlayer() const;
+	PlayerConnectionID getIdOfFirstUnallocatedPlayer() const;
 
 	void multiplayerWelcomeMessage();
 };

+ 40 - 0
server/IGameServer.h

@@ -0,0 +1,40 @@
+/*
+ * IGameServer.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/serializer/GameConnectionID.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+class PlayerColor;
+struct CPackForClient;
+
+VCMI_LIB_NAMESPACE_END
+
+enum class EServerState : ui8
+{
+	LOBBY,
+	GAMEPLAY,
+	SHUTDOWN
+};
+
+/// Interface through which GameHandler can interact with server that controls it
+class IGameServer
+{
+public:
+	virtual ~IGameServer() = default;
+
+	virtual void setState(EServerState value) = 0;
+	virtual EServerState getState() const = 0;
+	virtual bool isPlayerHost(const PlayerColor & color) const = 0;
+	virtual bool hasPlayerAt(PlayerColor player, GameConnectionID connectionID) const = 0;
+	virtual bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const = 0;
+	virtual void applyPack(CPackForClient & pack) = 0;
+	virtual void sendPack(CPackForClient & pack, GameConnectionID connectionID) = 0;
+};

+ 8 - 4
server/LobbyNetPackVisitors.h

@@ -11,15 +11,19 @@
 
 #include "../lib/networkPacks/NetPackVisitor.h"
 
+VCMI_LIB_NAMESPACE_BEGIN
+class GameConnection;
+VCMI_LIB_NAMESPACE_END
+
 class ClientPermissionsCheckerNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
 {
 private:
-	std::shared_ptr<CConnection> connection;
+	std::shared_ptr<GameConnection> connection;
 	CVCMIServer & srv;
 	bool result;
 
 public:
-	ClientPermissionsCheckerNetPackVisitor(CVCMIServer & srv, const std::shared_ptr<CConnection> & connection)
+	ClientPermissionsCheckerNetPackVisitor(CVCMIServer & srv, const std::shared_ptr<GameConnection> & connection)
 		: connection(connection)
 		, srv(srv)
 		, result(false)
@@ -67,12 +71,12 @@ public:
 class ApplyOnServerNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
 {
 private:
-	std::shared_ptr<CConnection> connection;
+	std::shared_ptr<GameConnection> connection;
 	CVCMIServer & srv;
 	bool result;
 
 public:
-	ApplyOnServerNetPackVisitor(CVCMIServer & srv, const std::shared_ptr<CConnection> & connection)
+	ApplyOnServerNetPackVisitor(CVCMIServer & srv, const std::shared_ptr<GameConnection> & connection)
 		: connection(connection)
 		, srv(srv)
 		, result(true)

+ 2 - 14
server/NetPacksLobbyServer.cpp

@@ -22,7 +22,7 @@
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/gameState/CGameState.h"
 #include "../lib/mapping/CMapInfo.h"
-#include "../lib/serializer/Connection.h"
+#include "../lib/serializer/GameConnection.h"
 
 void ClientPermissionsCheckerNetPackVisitor::visitForLobby(CPackForLobby & pack)
 {
@@ -220,19 +220,7 @@ void ApplyOnServerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
 
 void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
 {
-	if(pack.clientId == -1) //do not restart game for single client only
-		srv.startGameImmediately();
-	else
-	{
-		for(const auto & connection : srv.activeConnections)
-		{
-			if(connection->connectionID == pack.clientId)
-			{
-				connection->setCallback(srv.gh->gameInfo());
-				srv.reconnectPlayer(pack.clientId);
-			}
-		}
-	}
+	srv.startGameImmediately();
 }
 
 void ClientPermissionsCheckerNetPackVisitor::visitLobbyChangeHost(LobbyChangeHost & pack)

+ 2 - 2
server/ServerNetPackVisitors.h

@@ -14,12 +14,12 @@
 class ApplyGhNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
 {
 private:
-	std::shared_ptr<CConnection> connection;
+	GameConnectionID connection;
 	CGameHandler & gh;
 	bool result;
 
 public:
-	ApplyGhNetPackVisitor(CGameHandler & gh, const std::shared_ptr<CConnection> & connection)
+	ApplyGhNetPackVisitor(CGameHandler & gh, GameConnectionID connection)
 		: connection(connection)
 		, gh(gh)
 		, result(false)

+ 10 - 11
server/processors/PlayerMessageProcessor.cpp

@@ -32,7 +32,6 @@
 #include "../../lib/mapping/CMap.h"
 #include "../../lib/networkPacks/PacksForClient.h"
 #include "../../lib/networkPacks/StackLocation.h"
-#include "../../lib/serializer/Connection.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../lib/VCMIDirs.h"
 
@@ -70,17 +69,17 @@ void PlayerMessageProcessor::playerMessage(PlayerColor player, const std::string
 
 void PlayerMessageProcessor::commandExit(PlayerColor player, const std::vector<std::string> & words)
 {
-	bool isHost = gameHandler->gameLobby().isPlayerHost(player);
+	bool isHost = gameHandler->gameServer().isPlayerHost(player);
 	if(!isHost)
 		return;
 
 	broadcastSystemMessage(MetaString::createFromTextID("vcmi.broadcast.gameTerminated"));
-	gameHandler->gameLobby().setState(EServerState::SHUTDOWN);
+	gameHandler->gameServer().setState(EServerState::SHUTDOWN);
 }
 
 void PlayerMessageProcessor::commandKick(PlayerColor player, const std::vector<std::string> & words)
 {
-	bool isHost = gameHandler->gameLobby().isPlayerHost(player);
+	bool isHost = gameHandler->gameServer().isPlayerHost(player);
 	if(!isHost)
 		return;
 
@@ -112,7 +111,7 @@ void PlayerMessageProcessor::commandKick(PlayerColor player, const std::vector<s
 
 void PlayerMessageProcessor::commandSave(PlayerColor player, const std::vector<std::string> & words)
 {
-	bool isHost = gameHandler->gameLobby().isPlayerHost(player);
+	bool isHost = gameHandler->gameServer().isPlayerHost(player);
 	if(!isHost)
 		return;
 
@@ -147,7 +146,7 @@ void PlayerMessageProcessor::commandCheaters(PlayerColor player, const std::vect
 
 void PlayerMessageProcessor::commandStatistic(PlayerColor player, const std::vector<std::string> & words)
 {
-	bool isHost = gameHandler->gameLobby().isPlayerHost(player);
+	bool isHost = gameHandler->gameServer().isPlayerHost(player);
 	if(!isHost)
 		return;
 
@@ -964,25 +963,25 @@ void PlayerMessageProcessor::executeCheatCode(const std::string & cheatName, Pla
 		callbacks.at(cheatName)();
 }
 
-void PlayerMessageProcessor::sendSystemMessage(std::shared_ptr<CConnection> connection, const MetaString & message)
+void PlayerMessageProcessor::sendSystemMessage(GameConnectionID connectionID, const MetaString & message)
 {
 	SystemMessage sm;
 	sm.text = message;
-	connection->sendPack(sm);
+	gameHandler->gameServer().sendPack(sm, connectionID);
 }
 
-void PlayerMessageProcessor::sendSystemMessage(std::shared_ptr<CConnection> connection, const std::string & message)
+void PlayerMessageProcessor::sendSystemMessage(GameConnectionID connectionID, const std::string & message)
 {
 	MetaString str;
 	str.appendRawString(message);
-	sendSystemMessage(connection, str);
+	sendSystemMessage(connectionID, str);
 }
 
 void PlayerMessageProcessor::broadcastSystemMessage(MetaString message)
 {
 	SystemMessage sm;
 	sm.text = message;
-	gameHandler->sendToAllClients(sm);
+	gameHandler->gameServer().applyPack(sm);
 }
 
 void PlayerMessageProcessor::broadcastSystemMessage(const std::string & message)

+ 3 - 3
server/processors/PlayerMessageProcessor.h

@@ -10,11 +10,11 @@
 #pragma once
 
 #include "../../lib/GameConstants.h"
+#include "../../lib/serializer/GameConnectionID.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 class CGHeroInstance;
 class CGTownInstance;
-class CConnection;
 class MetaString;
 VCMI_LIB_NAMESPACE_END
 
@@ -80,8 +80,8 @@ public:
 	void playerMessage(PlayerColor player, const std::string & message, ObjectInstanceID currObj);
 
 	/// Send message to specific client with "System" as sender
-	void sendSystemMessage(std::shared_ptr<CConnection> connection, const MetaString & message);
-	void sendSystemMessage(std::shared_ptr<CConnection> connection, const std::string & message);
+	void sendSystemMessage(GameConnectionID connectionID, const MetaString & message);
+	void sendSystemMessage(GameConnectionID connectionID, const std::string & message);
 
 	/// Send message to all players with "System" as sender
 	void broadcastSystemMessage(MetaString message);

+ 1 - 1
server/processors/TurnOrderProcessor.cpp

@@ -260,7 +260,7 @@ void TurnOrderProcessor::doStartNewDay()
 
 	if(!activePlayer)
 	{
-		gameHandler->gameLobby().setState(EServerState::SHUTDOWN);
+		gameHandler->gameServer().setState(EServerState::SHUTDOWN);
 		return;
 	}
 

+ 1 - 1
test/game/CGameStateTest.cpp

@@ -165,7 +165,7 @@ public:
 
 			PlayerSettings & pset = si.playerInfos[PlayerColor(i)];
 			pset.color = PlayerColor(i);
-			pset.connectedPlayerIDs.insert(i);
+			pset.connectedPlayerIDs.insert(static_cast<PlayerConnectionID>(i));
 			pset.name = "Player";
 
 			pset.castle = pinfo.defaultCastle();

+ 1 - 1
test/mock/mock_IGameEventCallback.h

@@ -80,7 +80,7 @@ public:
 	void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator) override {}
 	void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override {} //when two heroes meet on adventure map
 	void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, ETileVisibility mode) override {}
-	void changeFogOfWar(const std::unordered_set<int3> &tiles, PlayerColor player, ETileVisibility mode) override {}
+	void changeFogOfWar(const FowTilesType &tiles, PlayerColor player, ETileVisibility mode) override {}
 	void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override {}
 
 	///useful callback methods

+ 2 - 2
test/mock/mock_IGameInfoCallback.h

@@ -66,8 +66,8 @@ public:
 	MOCK_CONST_METHOD1(getTopObj, const CGObjectInstance *(int3 pos));
 	MOCK_CONST_METHOD2(getTileDigStatus, EDiggingStatus(int3 tile, bool verbose));
 	MOCK_CONST_METHOD1(calculatePaths, void(const std::shared_ptr<PathfinderConfig> & config));
-	MOCK_CONST_METHOD6(getTilesInRange, void( std::unordered_set<int3> & tiles, const int3 & pos, int radius, ETileVisibility mode, std::optional<PlayerColor> player, int3::EDistanceFormula formula));
-	MOCK_CONST_METHOD4(getAllTiles, void(std::unordered_set<int3> & tiles, std::optional<PlayerColor> player, int level, const std::function<bool(const TerrainTile *)> & filter));
+	MOCK_CONST_METHOD6(getTilesInRange, void(FowTilesType & tiles, const int3 & pos, int radius, ETileVisibility mode, std::optional<PlayerColor> player, int3::EDistanceFormula formula));
+	MOCK_CONST_METHOD4(getAllTiles, void(FowTilesType & tiles, std::optional<PlayerColor> player, int level, const std::function<bool(const TerrainTile *)> & filter));
 	MOCK_CONST_METHOD2(getVisibleTeleportObjects, std::vector<ObjectInstanceID>(std::vector<ObjectInstanceID> ids, PlayerColor player));
 	MOCK_CONST_METHOD2(getTeleportChannelEntrances, std::vector<ObjectInstanceID>(TeleportChannelID id, PlayerColor Player));
 	MOCK_CONST_METHOD2(getTeleportChannelExits, std::vector<ObjectInstanceID>(TeleportChannelID id, PlayerColor Player));