Browse Source

Merge pull request #1052 from Nordsoft91/mp-disconnection

MP handle disconnection
Andrii Danylchenko 3 years ago
parent
commit
288f36c0c3

+ 2 - 0
CCallback.cpp

@@ -284,6 +284,8 @@ void CCallback::sendMessage(const std::string &mess, const CGObjectInstance * cu
 {
 	ASSERT_IF_CALLED_WITH_PLAYER
 	PlayerMessage pm(mess, currentObject? currentObject->id : ObjectInstanceID(-1));
+	if(player)
+		pm.player = *player;
 	sendRequest(&pm);
 }
 

+ 7 - 0
client/CMT.cpp

@@ -399,6 +399,7 @@ int main(int argc, char * argv[])
 	CCS = new CClientState();
 	CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.)
 	CSH = new CServerHandler();
+	
 	// Initialize video
 #ifdef DISABLE_VIDEO
 	CCS->videoh = new CEmptyVideoPlayer();
@@ -499,6 +500,12 @@ int main(int argc, char * argv[])
 	{
 		GH.curInt = CMainMenu::create().get();
 	}
+	
+	// Restore remote session - start game immediately
+	if(settings["server"]["reconnect"].Bool())
+	{
+		CSH->restoreLastSession();
+	}
 
 	if(!settings["session"]["headless"].Bool())
 	{

+ 43 - 0
client/CServerHandler.cpp

@@ -559,6 +559,23 @@ void CServerHandler::startGameplay(CGameState * gameState)
 	// After everything initialized we can accept CPackToClient netpacks
 	c->enterGameplayConnectionMode(client->gameState());
 	state = EClientState::GAMEPLAY;
+	
+	//store settings to continue game
+	if(!isServerLocal() && isGuest())
+	{
+		Settings saveSession = settings.write["server"]["reconnect"];
+		saveSession->Bool() = true;
+		Settings saveUuid = settings.write["server"]["uuid"];
+		saveUuid->String() = uuid;
+		Settings saveNames = settings.write["server"]["names"];
+		saveNames->Vector().clear();
+		for(auto & name : myNames)
+		{
+			JsonNode jsonName;
+			jsonName.String() = name;
+			saveNames->Vector().push_back(jsonName);
+		}
+	}
 }
 
 void CServerHandler::endGameplay(bool closeConnection, bool restart)
@@ -589,6 +606,10 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart)
 	
 	c->enterLobbyConnectionMode();
 	c->disableStackSendingByID();
+	
+	//reset settings
+	Settings saveSession = settings.write["server"]["reconnect"];
+	saveSession->Bool() = false;
 }
 
 void CServerHandler::startCampaignScenario(std::shared_ptr<CCampaignState> cs)
@@ -639,6 +660,28 @@ ui8 CServerHandler::getLoadMode()
 	return loadMode;
 }
 
+void CServerHandler::restoreLastSession()
+{
+	auto loadSession = [this]()
+	{
+		uuid = settings["server"]["uuid"].String();
+		for(auto & name : settings["server"]["names"].Vector())
+			myNames.push_back(name.String());
+		resetStateForLobby(StartInfo::LOAD_GAME, &myNames);
+		screenType = ESelectionScreen::loadGame;
+		justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer());
+	};
+	
+	auto cleanUpSession = []()
+	{
+		//reset settings
+		Settings saveSession = settings.write["server"]["reconnect"];
+		saveSession->Bool() = false;
+	};
+	
+	CInfoWindow::showYesNoDialog(VLC->generaltexth->localizedTexts["server"]["confirmReconnect"].String(), {}, loadSession, cleanUpSession);
+}
+
 void CServerHandler::debugStartTest(std::string filename, bool save)
 {
 	logGlobal->info("Starting debug test with file: %s", filename);

+ 1 - 0
client/CServerHandler.h

@@ -157,6 +157,7 @@ public:
 	ui8 getLoadMode();
 
 	void debugStartTest(std::string filename, bool save = false);
+	void restoreLastSession();
 };
 
 extern CServerHandler * CSH;

+ 33 - 1
client/NetPacksClient.cpp

@@ -345,6 +345,34 @@ void PlayerEndsGame::applyCl(CClient *cl)
 		handleQuit(settings["session"]["spectate"].Bool()); // if spectator is active ask to close client or not
 }
 
+void PlayerReinitInterface::applyCl(CClient * cl)
+{
+	auto initInterfaces = [cl]()
+	{
+		cl->initPlayerInterfaces();
+		auto currentPlayer = cl->gameState()->currentPlayer;
+		callAllInterfaces(cl, &IGameEventsReceiver::playerStartsTurn, currentPlayer);
+		callOnlyThatInterface(cl, currentPlayer, &CGameInterface::yourTurn);
+	};
+	
+	for(auto player : players)
+	{
+		auto & plSettings = CSH->si->getIthPlayersSettings(player);
+		if(playerConnectionId == PlayerSettings::PLAYER_AI)
+		{
+			plSettings.connectedPlayerIDs.clear();
+			cl->initPlayerEnvironments();
+			initInterfaces();
+		}
+		else if(playerConnectionId == CSH->c->connectionID)
+		{
+			plSettings.connectedPlayerIDs.insert(playerConnectionId);
+			cl->playerint.clear();
+			initInterfaces();
+		}
+	}
+}
+
 void RemoveBonus::applyCl(CClient *cl)
 {
 	cl->invalidatePaths();
@@ -790,7 +818,11 @@ void YourTurn::applyCl(CClient *cl)
 void SaveGameClient::applyCl(CClient *cl)
 {
 	const auto stem = FileInfo::GetPathStem(fname);
-	CResourceHandler::get("local")->createResource(stem.to_string() + ".vcgm1");
+	if(!CResourceHandler::get("local")->createResource(stem.to_string() + ".vcgm1"))
+	{
+		logNetwork->error("Failed to create resource %s", stem.to_string() + ".vcgm1");
+		return;
+	}
 
 	try
 	{

+ 16 - 2
client/NetPacksLobbyClient.cpp

@@ -74,7 +74,7 @@ void LobbyChatMessage::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler *
 
 void LobbyGuiAction::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
 {
-	if(!handler->isGuest())
+	if(!lobby || !handler->isGuest())
 		return;
 
 	switch(action)
@@ -109,10 +109,15 @@ bool LobbyEndGame::applyOnLobbyHandler(CServerHandler * handler)
 
 bool LobbyStartGame::applyOnLobbyHandler(CServerHandler * handler)
 {
+	if(clientId != -1 && clientId != handler->c->connectionID)
+		return false;
+	
 	handler->state = EClientState::STARTING;
-	if(handler->si->mode != StartInfo::LOAD_GAME)
+	if(handler->si->mode != StartInfo::LOAD_GAME || clientId == handler->c->connectionID)
 	{
+		auto modeBackup = handler->si->mode;
 		handler->si = initializedStartInfo;
+		handler->si->mode = modeBackup;
 	}
 	if(settings["session"]["headless"].Bool())
 		handler->startGameplay(initializedGameState);
@@ -121,6 +126,9 @@ bool LobbyStartGame::applyOnLobbyHandler(CServerHandler * handler)
 
 void LobbyStartGame::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
 {
+	if(clientId != -1 && clientId != handler->c->connectionID)
+		return;
+	
 	GH.pushIntT<CLoadingScreen>(std::bind(&CServerHandler::startGameplay, handler, initializedGameState));
 }
 
@@ -133,6 +141,9 @@ bool LobbyUpdateState::applyOnLobbyHandler(CServerHandler * handler)
 
 void LobbyUpdateState::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
 {
+	if(!lobby) //stub: ignore message for game mode
+		return;
+		
 	if(!lobby->bonusSel && handler->si->campState && handler->state == EClientState::LOBBY_CAMPAIGN)
 	{
 		lobby->bonusSel = std::make_shared<CBonusSelection>();
@@ -150,6 +161,9 @@ void LobbyUpdateState::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler *
 
 void LobbyShowMessage::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
 {
+	if(!lobby) //stub: ignore message for game mode
+		return;
+	
 	lobby->buttonStart->block(false);
 	handler->showServerError(message);
 }

+ 4 - 6
client/windows/CAdvmapInterface.cpp

@@ -934,6 +934,10 @@ void CAdvMapInt::activate()
 
 	screenBuf = screen;
 	GH.statusbar = statusbar;
+	
+	if(LOCPLINT)
+		LOCPLINT->cingconsole->activate();
+	
 	if(!duringAITurn)
 	{
 		activeMapPanel->activate();
@@ -945,8 +949,6 @@ void CAdvMapInt::activate()
 		}
 		minimap.activate();
 		terrain.activate();
-		if(LOCPLINT)
-			LOCPLINT->cingconsole->activate();
 
 		GH.fakeMouseMove(); //to restore the cursor
 	}
@@ -970,8 +972,6 @@ void CAdvMapInt::deactivate()
 		}
 		minimap.deactivate();
 		terrain.deactivate();
-		if(LOCPLINT)
-			LOCPLINT->cingconsole->deactivate();
 	}
 }
 
@@ -1532,8 +1532,6 @@ void CAdvMapInt::endingTurn()
 	if(settings["session"]["spectate"].Bool())
 		return;
 
-	if(LOCPLINT->cingconsole->active)
-		LOCPLINT->cingconsole->deactivate();
 	LOCPLINT->makingTurn = false;
 	LOCPLINT->cb->endTurn();
 	CCS->soundh->ambientStopAllChannels();

+ 18 - 1
config/schemas/settings.json

@@ -253,7 +253,7 @@
 			"type" : "object",
 			"additionalProperties" : false,
 			"default": {},
-			"required" : [ "server", "port", "localInformation", "playerAI", "friendlyAI","neutralAI", "enemyAI" ],
+			"required" : [ "server", "port", "localInformation", "playerAI", "friendlyAI","neutralAI", "enemyAI", "reconnect", "uuid", "names" ],
 			"properties" : {
 				"server" : {
 					"type":"string",
@@ -282,6 +282,23 @@
 				"enemyAI" : {
 					"type" : "string",
 					"default" : "BattleAI"
+				},
+				"reconnect" : {
+					"type" : "boolean",
+					"default" : false
+				},
+				"uuid" : {
+					"type" : "string",
+					"default" : ""
+				},
+				"names" : {
+					"type" : "array",
+					"default" : {},
+					"items":
+					{
+						"type" : "string",
+						"default" : ""
+					}
 				}
 			}
 		},

+ 2 - 1
config/translate.json

@@ -33,7 +33,8 @@
 		{
 			"existingProcess" : "Another vcmiserver process is running, please terminate it first",
 			"modsIncompatibility" : "Required mods to load game:"
-		}
+		},
+		"confirmReconnect" : "Connect to the last session?"
 	},
 	"systemOptions" :
 	{

+ 15 - 0
lib/NetPacks.h

@@ -421,6 +421,21 @@ struct PlayerEndsGame : public CPackForClient
 	}
 };
 
+struct PlayerReinitInterface : public CPackForClient
+{
+	void applyCl(CClient * cl);
+	DLL_LINKAGE void applyGs(CGameState *gs);
+	
+	std::vector<PlayerColor> players;
+	ui8 playerConnectionId; //PLAYER_AI for AI player
+	
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & players;
+		h & playerConnectionId;
+	}
+};
+
 struct RemoveBonus :  public CPackForClient
 {
 	RemoveBonus(ui8 Who = 0)

+ 13 - 0
lib/NetPacksLib.cpp

@@ -365,6 +365,19 @@ DLL_LINKAGE void PlayerEndsGame::applyGs(CGameState *gs)
 	}
 }
 
+DLL_LINKAGE void PlayerReinitInterface::applyGs(CGameState *gs)
+{
+	if(!gs || !gs->scenarioOps)
+		return;
+	
+	//TODO: what does mean if more that one player connected?
+	if(playerConnectionId == PlayerSettings::PLAYER_AI)
+	{
+		for(auto player : players)
+			gs->scenarioOps->getIthPlayersSettings(player).connectedPlayerIDs.clear();
+	}
+}
+
 DLL_LINKAGE void RemoveBonus::applyGs(CGameState *gs)
 {
 	CBonusSystemNode *node;

+ 3 - 1
lib/NetPacksLobby.h

@@ -159,8 +159,9 @@ struct LobbyStartGame : public CLobbyPackToPropagate
 	// Set by server
 	std::shared_ptr<StartInfo> initializedStartInfo;
 	CGameState * initializedGameState;
+	int clientId; //-1 means to all clients
 
-	LobbyStartGame() : initializedStartInfo(nullptr), initializedGameState(nullptr) {}
+	LobbyStartGame() : initializedStartInfo(nullptr), initializedGameState(nullptr), clientId(-1) {}
 	bool checkClientPermissions(CVCMIServer * srv) const;
 	bool applyOnServer(CVCMIServer * srv);
 	void applyOnServerAfterAnnounce(CVCMIServer * srv);
@@ -169,6 +170,7 @@ struct LobbyStartGame : public CLobbyPackToPropagate
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & clientId;
 		h & initializedStartInfo;
 		bool sps = h.smartPointerSerialization;
 		h.smartPointerSerialization = true;

+ 1 - 0
lib/registerTypes/RegisterTypes.h

@@ -240,6 +240,7 @@ void registerTypesClientPacks1(Serializer &s)
 	s.template registerType<CPackForClient, GiveBonus>();
 	s.template registerType<CPackForClient, ChangeObjPos>();
 	s.template registerType<CPackForClient, PlayerEndsGame>();
+	s.template registerType<CPackForClient, PlayerReinitInterface>();
 	s.template registerType<CPackForClient, RemoveBonus>();
 	s.template registerType<CPackForClient, UpdateArtHandlerLists>();
 	s.template registerType<CPackForClient, UpdateMapEvents>();

+ 92 - 31
server/CGameHandler.cpp

@@ -1324,23 +1324,21 @@ void CGameHandler::addGenericKilledLog(BattleLogMessage & blm, const CStack * de
 
 void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
 {
-	for(auto playerConns : connections)
+	if(lobby->state == EServerState::SHUTDOWN || !gs || !gs->scenarioOps)
+		return;
+	
+	for(auto & playerConnections : connections)
 	{
-		for(auto i = playerConns.second.begin(); i != playerConns.second.end(); )
+		PlayerColor playerId = playerConnections.first;
+		auto * playerSettings = gs->scenarioOps->getPlayersSettings(playerId.getNum());
+		if(!playerSettings)
+			continue;
+		
+		auto playerConnection = vstd::find(playerConnections.second, c);
+		if(playerConnection != playerConnections.second.end())
 		{
-			if(lobby->state != EServerState::SHUTDOWN && *i == c)
-			{
-				i = playerConns.second.erase(i);
-				if(playerConns.second.size())
-					continue;
-				PlayerCheated pc;
-				pc.player = playerConns.first;
-				pc.losingCheatCode = true;
-				sendAndApply(&pc);
-				checkVictoryLossConditionsForPlayer(playerConns.first);
-			}
-			else
-				++i;
+			std::string messageText = boost::str(boost::format("%s (cid %d) was disconnected") % playerSettings->name % c->connectionID);
+			playerMessage(playerId, messageText, ObjectInstanceID{});
 		}
 	}
 }
@@ -3396,6 +3394,11 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8
 	return true;
 }
 
+bool CGameHandler::hasPlayerAt(PlayerColor player, std::shared_ptr<CConnection> c) const
+{
+	return connections.at(player).count(c);
+}
+
 PlayerColor CGameHandler::getPlayerAt(std::shared_ptr<CConnection> c) const
 {
 	std::set<PlayerColor> all;
@@ -5003,16 +5006,65 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 
 void CGameHandler::playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj)
 {
-	bool cheated = true;
+	bool cheated = false;
 	PlayerMessageClient temp_message(player, message);
 	sendAndApply(&temp_message);
 
-	std::vector<std::string> cheat;
-	boost::split(cheat, message, boost::is_any_of(" "));
+	std::vector<std::string> words;
+	boost::split(words, message, boost::is_any_of(" "));
+	
+	bool isHost = false;
+	for(auto & c : connections[player])
+		if(lobby->isClientHost(c->connectionID))
+			isHost = true;
+	
+	if(isHost && words.size() >= 2 && words[0] == "game")
+	{
+		if(words[1] == "exit" || words[1] == "quit" || words[1] == "end")
+		{
+			SystemMessage temp_message("game was terminated");
+			sendAndApply(&temp_message);
+			lobby->state = EServerState::SHUTDOWN;
+			return;
+		}
+		if(words.size() == 3 && words[1] == "save")
+		{
+			save("Saves/" + words[2]);
+			SystemMessage temp_message("game saved as " + words[2]);
+			sendAndApply(&temp_message);
+			return;
+		}
+		if(words.size() == 3 && words[1] == "kick")
+		{
+			auto playername = words[2];
+			PlayerColor playerToKick(PlayerColor::CANNOT_DETERMINE);
+			if(std::all_of(playername.begin(), playername.end(), ::isdigit))
+				playerToKick = PlayerColor(std::stoi(playername));
+			else
+			{
+				for(auto & c : connections)
+				{
+					if(c.first.getStr(false) == playername)
+						playerToKick = c.first;
+				}
+			}
+			
+			if(playerToKick != PlayerColor::CANNOT_DETERMINE)
+			{
+				PlayerCheated pc;
+				pc.player = playerToKick;
+				pc.losingCheatCode = true;
+				sendAndApply(&pc);
+				checkVictoryLossConditionsForPlayer(playerToKick);
+			}
+			return;
+		}
+	}
+	
 	int obj = 0;
-	if (cheat.size() == 2)
+	if (words.size() == 2)
 	{
-		obj = std::atoi(cheat[1].c_str());
+		obj = std::atoi(words[1].c_str());
 		if (obj)
 			currObj = ObjectInstanceID(obj);
 	}
@@ -5022,38 +5074,38 @@ void CGameHandler::playerMessage(PlayerColor player, const std::string &message,
 	if (!town && hero)
 		town = hero->visitedTown;
 
-	if (cheat.size() == 1 || obj)
-		handleCheatCode(cheat[0], player, hero, town, cheated);
+	if (words.size() == 1 || obj)
+		handleCheatCode(words[0], player, hero, town, cheated);
 	else
 	{
 		for (const auto & i : gs->players)
 		{
 			if (i.first == PlayerColor::NEUTRAL)
 				continue;
-			if (cheat[1] == "ai")
+			if (words[1] == "ai")
 			{
 				if (i.second.human)
 					continue;
 			}
-			else if (cheat[1] != "all" && cheat[1] != i.first.getStr())
+			else if (words[1] != "all" && words[1] != i.first.getStr())
 				continue;
 
-			if (cheat[0] == "vcmiformenos" || cheat[0] == "vcmieagles" || cheat[0] == "vcmiungoliant")
+			if (words[0] == "vcmiformenos" || words[0] == "vcmieagles" || words[0] == "vcmiungoliant")
 			{
-				handleCheatCode(cheat[0], i.first, nullptr, nullptr, cheated);
+				handleCheatCode(words[0], i.first, nullptr, nullptr, cheated);
 			}
-			else if (cheat[0] == "vcmiarmenelos")
+			else if (words[0] == "vcmiarmenelos")
 			{
 				for (const auto & t : i.second.towns)
 				{
-					handleCheatCode(cheat[0], i.first, nullptr, t, cheated);
+					handleCheatCode(words[0], i.first, nullptr, t, cheated);
 				}
 			}
 			else
 			{
 				for (const auto & h : i.second.heroes)
 				{
-					handleCheatCode(cheat[0], i.first, h, nullptr, cheated);
+					handleCheatCode(words[0], i.first, h, nullptr, cheated);
 				}
 			}
 		}
@@ -6931,6 +6983,7 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 {
 	if (cheat == "vcmiistari")
 	{
+		cheated = true;
 		if (!hero) return;
 		///Give hero spellbook
 		if (!hero->hasSpellbook())
@@ -6956,6 +7009,7 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 	}
 	else if (cheat == "vcmiarmenelos")
 	{
+		cheated = true;
 		if (!town) return;
 		///Build all buildings in selected town
 		for (auto & build : town->town->buildings)
@@ -6970,6 +7024,7 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 	}
 	else if (cheat == "vcmiainur" || cheat == "vcmiangband" || cheat == "vcmiglaurung")
 	{
+		cheated = true;
 		if (!hero) return;
 		///Gives N creatures into each slot
 		std::map<std::string, std::pair<int, int>> creatures;
@@ -6984,6 +7039,7 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 	}
 	else if (cheat == "vcminoldor")
 	{
+		cheated = true;
 		if (!hero) return;
 		///Give all war machines to hero
 		if (!hero->getArt(ArtifactPosition::MACH1))
@@ -6995,6 +7051,7 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 	}
 	else if (cheat == "vcmiforgeofnoldorking")
 	{
+		cheated = true;
 		if (!hero) return;
 		///Give hero all artifacts except war machines, spell scrolls and spell book
 		for (int g = 7; g < VLC->arth->objects.size(); ++g) //including artifacts from mods
@@ -7002,12 +7059,14 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 	}
 	else if (cheat == "vcmiglorfindel")
 	{
+		cheated = true;
 		if (!hero) return;
 		///selected hero gains a new level
 		changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level + 1) - VLC->heroh->reqExp(hero->level));
 	}
 	else if (cheat == "vcminahar")
 	{
+		cheated = true;
 		if (!hero) return;
 		///Give 1000000 movement points to hero
 		SetMovePoints smp;
@@ -7024,6 +7083,7 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 	}
 	else if (cheat == "vcmiformenos")
 	{
+		cheated = true;
 		///Give resources to player
 		TResources resources;
 		resources[Res::GOLD] = 100000;
@@ -7034,6 +7094,7 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 	}
 	else if (cheat == "vcmisilmaril")
 	{
+		cheated = true;
 		///Player wins
 		PlayerCheated pc;
 		pc.player = player;
@@ -7042,6 +7103,7 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 	}
 	else if (cheat == "vcmimelkor")
 	{
+		cheated = true;
 		///Player looses
 		PlayerCheated pc;
 		pc.player = player;
@@ -7050,6 +7112,7 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 	}
 	else if (cheat == "vcmieagles" || cheat == "vcmiungoliant")
 	{
+		cheated = true;
 		///Reveal or conceal FoW
 		FoWChange fc;
 		fc.mode = (cheat == "vcmieagles" ? 1 : 0);
@@ -7068,8 +7131,6 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 		delete [] hlp_tab;
 		sendAndApply(&fc);
 	}
-	else
-		cheated = false;
 }
 
 void CGameHandler::removeObstacle(const CObstacleInstance & obstacle)

+ 1 - 0
server/CGameHandler.h

@@ -223,6 +223,7 @@ public:
 	void handleClientDisconnection(std::shared_ptr<CConnection> c);
 	void handleReceivedPack(CPackForServer * pack);
 	PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const;
+	bool hasPlayerAt(PlayerColor player, std::shared_ptr<CConnection> c) const;
 
 	void playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj);
 	void updateGateState();

+ 92 - 34
server/CVCMIServer.cpp

@@ -206,11 +206,6 @@ void CVCMIServer::threadAnnounceLobby()
 				announcePack(std::move(announceQueue.front()));
 				announceQueue.pop_front();
 			}
-			if(state != EServerState::LOBBY)
-			{
-				if(acceptor)
-					acceptor->close();
-			}
 
 			if(acceptor)
 			{
@@ -308,11 +303,14 @@ void CVCMIServer::connectionAccepted(const boost::system::error_code & ec)
 
 	try
 	{
-		logNetwork->info("We got a new connection! :)");
-		auto c = std::make_shared<CConnection>(upcomingConnection, SERVER_NAME, uuid);
-		upcomingConnection.reset();
-		connections.insert(c);
-		c->handler = std::make_shared<boost::thread>(&CVCMIServer::threadHandleClient, this, c);
+		if(state == EServerState::LOBBY || !hangingConnections.empty())
+		{
+			logNetwork->info("We got a new connection! :)");
+			auto c = std::make_shared<CConnection>(upcomingConnection, SERVER_NAME, uuid);
+			upcomingConnection.reset();
+			connections.insert(c);
+			c->handler = std::make_shared<boost::thread>(&CVCMIServer::threadHandleClient, this, c);
+		}
 	}
 	catch(std::exception & e)
 	{
@@ -343,10 +341,15 @@ void CVCMIServer::threadHandleClient(std::shared_ptr<CConnection> c)
 			catch(boost::system::system_error & e)
 			{
 				logNetwork->error("Network error receiving a pack. Connection %s dies. What happened: %s", c->toString(), e.what());
-
-				if(state != EServerState::LOBBY)
+				hangingConnections.insert(c);
+				connections.erase(c);
+				if(connections.empty() || hostClient == c)
+					state = EServerState::SHUTDOWN;
+				
+				if(gh && state == EServerState::GAMEPLAY)
+				{
 					gh->handleClientDisconnection(c);
-
+				}
 				break;
 			}
 			
@@ -453,7 +456,8 @@ bool CVCMIServer::passHost(int toConnectionId)
 
 void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<std::string> & names, std::string uuid, StartInfo::EMode mode)
 {
-	c->connectionID = currentClientId++;
+	if(state == EServerState::LOBBY)
+		c->connectionID = currentClientId++;
 
 	if(!hostClient)
 	{
@@ -463,24 +467,28 @@ void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<st
 	}
 
 	logNetwork->info("Connection with client %d established. UUID: %s", c->connectionID, c->uuid);
-	for(auto & name : names)
+	
+	if(state == EServerState::LOBBY)
 	{
-		logNetwork->info("Client %d player: %s", c->connectionID, name);
-		ui8 id = currentPlayerId++;
+		for(auto & name : names)
+		{
+			logNetwork->info("Client %d player: %s", c->connectionID, name);
+			ui8 id = currentPlayerId++;
 
-		ClientPlayer cp;
-		cp.connection = c->connectionID;
-		cp.name = name;
-		playerNames.insert(std::make_pair(id, cp));
-		announceTxt(boost::str(boost::format("%s (pid %d cid %d) joins the game") % name % id % c->connectionID));
+			ClientPlayer cp;
+			cp.connection = c->connectionID;
+			cp.name = name;
+			playerNames.insert(std::make_pair(id, cp));
+			announceTxt(boost::str(boost::format("%s (pid %d cid %d) joins the game") % name % id % c->connectionID));
 
-		//put new player in first slot with AI
-		for(auto & elem : si->playerInfos)
-		{
-			if(elem.second.isControlledByAI() && !elem.second.compOnly)
+			//put new player in first slot with AI
+			for(auto & elem : si->playerInfos)
 			{
-				setPlayerConnectedId(elem.second, id);
-				break;
+				if(elem.second.isControlledByAI() && !elem.second.compOnly)
+				{
+					setPlayerConnectedId(elem.second, id);
+					break;
+				}
 			}
 		}
 	}
@@ -489,23 +497,73 @@ void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<st
 void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c)
 {
 	connections -= c;
+	if(connections.empty() || hostClient == c)
+	{
+		state = EServerState::SHUTDOWN;
+		return;
+	}
+	
+	PlayerReinitInterface startAiPack;
+	startAiPack.playerConnectionId = PlayerSettings::PLAYER_AI;
+	
 	for(auto it = playerNames.begin(); it != playerNames.end();)
 	{
 		if(it->second.connection != c->connectionID)
 		{
-			it++;
+			++it;
 			continue;
 		}
 
 		int id = it->first;
-		announceTxt(boost::str(boost::format("%s (pid %d cid %d) left the game") % id % playerNames[id].name % c->connectionID));
-		playerNames.erase(it++);
+		std::string playerLeftMsgText = boost::str(boost::format("%s (pid %d cid %d) left the game") % id % playerNames[id].name % c->connectionID);
+		announceTxt(playerLeftMsgText); //send lobby text, it will be ignored for non-lobby clients
+		auto * playerSettings = si->getPlayersSettings(id);
+		if(!playerSettings)
+		{
+			++it;
+			continue;
+		}
+		
+		it = playerNames.erase(it);
+		setPlayerConnectedId(*playerSettings, PlayerSettings::PLAYER_AI);
+		
+		if(gh && si && state == EServerState::GAMEPLAY)
+		{
+			gh->playerMessage(playerSettings->color, playerLeftMsgText, ObjectInstanceID{});
+			gh->connections[playerSettings->color].insert(hostClient);
+			startAiPack.players.push_back(playerSettings->color);
+		}
+	}
+	
+	if(!startAiPack.players.empty())
+		gh->sendAndApply(&startAiPack);
+}
 
-		// Reset in-game players client used back to AI
-		if(PlayerSettings * s = si->getPlayersSettings(id))
+void CVCMIServer::reconnectPlayer(int connId)
+{
+	PlayerReinitInterface startAiPack;
+	startAiPack.playerConnectionId = connId;
+	
+	if(gh && si && state == EServerState::GAMEPLAY)
+	{
+		for(auto it = playerNames.begin(); it != playerNames.end(); ++it)
 		{
-			setPlayerConnectedId(*s, PlayerSettings::PLAYER_AI);
+			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->playerMessage(playerSettings->color, messageText, ObjectInstanceID{});
+			
+			startAiPack.players.push_back(playerSettings->color);
 		}
+
+		if(!startAiPack.players.empty())
+			gh->sendAndApply(&startAiPack);
 	}
 }
 

+ 3 - 0
server/CVCMIServer.h

@@ -61,6 +61,8 @@ public:
 
 	boost::program_options::variables_map cmdLineOptions;
 	std::set<std::shared_ptr<CConnection>> connections;
+	std::set<std::shared_ptr<CConnection>> hangingConnections; //keep connections of players disconnected during the game
+	
 	std::atomic<int> currentClientId;
 	std::atomic<ui8> currentPlayerId;
 	std::shared_ptr<CConnection> hostClient;
@@ -90,6 +92,7 @@ public:
 
 	void clientConnected(std::shared_ptr<CConnection> c, std::vector<std::string> & names, std::string uuid, StartInfo::EMode mode);
 	void clientDisconnected(std::shared_ptr<CConnection> c);
+	void reconnectPlayer(int connId);
 
 	void updateAndPropagateLobbyState();
 

+ 71 - 3
server/NetPacksLobbyServer.cpp

@@ -34,11 +34,58 @@ void CLobbyPackToServer::applyOnServerAfterAnnounce(CVCMIServer * srv)
 
 bool LobbyClientConnected::checkClientPermissions(CVCMIServer * srv) const
 {
-	return true;
+	if(srv->gh)
+	{
+		for(auto & connection : srv->hangingConnections)
+		{
+			if(connection->uuid == uuid)
+			{
+				return true;
+			}
+		}
+	}
+	
+	if(srv->state == EServerState::LOBBY)
+		return true;
+	
+	//disconnect immediately and ignore this client
+	srv->connections.erase(c);
+	if(c && c->isOpen())
+	{
+		c->close();
+		c->connected = false;
+	}
+	return false;
 }
 
 bool LobbyClientConnected::applyOnServer(CVCMIServer * srv)
 {
+	if(srv->gh)
+	{
+		for(auto & connection : srv->hangingConnections)
+		{
+			if(connection->uuid == uuid)
+			{
+				logNetwork->info("Reconnection player");
+				c->connectionID = connection->connectionID;
+				for(auto & playerConnection : srv->gh->connections)
+				{
+					for(auto & existingConnection : playerConnection.second)
+					{
+						if(existingConnection == connection)
+						{
+							playerConnection.second.erase(existingConnection);
+							playerConnection.second.insert(c);
+							break;
+						}
+					}
+				}
+				srv->hangingConnections.erase(connection);
+				break;
+			}
+		}
+	}
+	
 	srv->clientConnected(c, names, uuid, mode);
 	// Server need to pass some data to newly connected client
 	clientId = c->connectionID;
@@ -53,6 +100,15 @@ void LobbyClientConnected::applyOnServerAfterAnnounce(CVCMIServer * srv)
 	// Until UUID set we only pass LobbyClientConnected to this client
 	c->uuid = uuid;
 	srv->updateAndPropagateLobbyState();
+	if(srv->state == EServerState::GAMEPLAY)
+	{
+		//immediately start game
+		std::unique_ptr<LobbyStartGame> startGameForReconnectedPlayer(new LobbyStartGame);
+		startGameForReconnectedPlayer->initializedStartInfo = srv->si;
+		startGameForReconnectedPlayer->initializedGameState = srv->gh->gameState();
+		startGameForReconnectedPlayer->clientId = c->connectionID;
+		srv->addToAnnounceQueue(std::move(startGameForReconnectedPlayer));
+	}
 }
 
 bool LobbyClientDisconnected::checkClientPermissions(CVCMIServer * srv) const
@@ -82,7 +138,7 @@ bool LobbyClientDisconnected::applyOnServer(CVCMIServer * srv)
 
 void LobbyClientDisconnected::applyOnServerAfterAnnounce(CVCMIServer * srv)
 {
-	if(c->isOpen())
+	if(c && c->isOpen())
 	{
 		boost::unique_lock<boost::mutex> lock(*c->mutexWrite);
 		c->close();
@@ -209,7 +265,19 @@ bool LobbyStartGame::applyOnServer(CVCMIServer * srv)
 
 void LobbyStartGame::applyOnServerAfterAnnounce(CVCMIServer * srv)
 {
-	srv->startGameImmidiately();
+	if(clientId == -1) //do not restart game for single client only
+		srv->startGameImmidiately();
+	else
+	{
+		for(auto & c : srv->connections)
+		{
+			if(c->connectionID == clientId)
+			{
+				c->enterGameplayConnectionMode(srv->gh->gameState());
+				srv->reconnectPlayer(clientId);
+			}
+		}
+	}
 }
 
 bool LobbyChangeHost::checkClientPermissions(CVCMIServer * srv) const

+ 2 - 5
server/NetPacksServer.cpp

@@ -61,7 +61,7 @@ void CPackForServer::throwOnWrongOwner(CGameHandler * gh, ObjectInstanceID id)
 
 void CPackForServer::throwOnWrongPlayer(CGameHandler * gh, PlayerColor player)
 {
-	if(player != gh->getPlayerAt(c))
+	if(!gh->hasPlayerAt(player, c) && player != gh->getPlayerAt(c))
 	{
 		wrongPlayerMessage(gh, player);
 		throwNotAllowedAction();
@@ -381,11 +381,8 @@ bool CastAdvSpell::applyGh(CGameHandler * gh)
 bool PlayerMessage::applyGh(CGameHandler * gh)
 {
 	if(!player.isSpectator()) // TODO: clearly not a great way to verify permissions
-	{
 		throwOnWrongPlayer(gh, player);
-		if(gh->getPlayerAt(this->c) != player)
-			throwNotAllowedAction();
-	}
+	
 	gh->playerMessage(player, text, currObj);
 	return true;
 }