Browse Source

Merge pull request #1000 from vcmi/multiplayer

Multiplayer
Andrii Danylchenko 3 years ago
parent
commit
04490b709a

+ 1 - 1
client/CMT.cpp

@@ -1367,7 +1367,7 @@ static void handleEvent(SDL_Event & ev)
 			break;
 		case EUserEvent::RESTART_GAME:
 			{
-				CSH->sendStartGame();
+				CSH->sendRestartGame();
 			}
 			break;
 		case EUserEvent::CAMPAIGN_START_SCENARIO:

+ 17 - 4
client/CServerHandler.cpp

@@ -512,6 +512,14 @@ void CServerHandler::sendGuiAction(ui8 action) const
 	sendLobbyPack(lga);
 }
 
+void CServerHandler::sendRestartGame() const
+{
+	LobbyEndGame endGame;
+	endGame.closeConnection = false;
+	endGame.restart = true;
+	sendLobbyPack(endGame);
+}
+
 void CServerHandler::sendStartGame(bool allowOnlyAI) const
 {
 	verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
@@ -524,9 +532,11 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const
 		* si = * lsg.initializedStartInfo;
 	}
 	sendLobbyPack(lsg);
+	c->enterLobbyConnectionMode();
+	c->disableStackSendingByID();
 }
 
-void CServerHandler::startGameplay()
+void CServerHandler::startGameplay(CGameState * gameState)
 {
 	if(CMM)
 		CMM->disable();
@@ -535,13 +545,13 @@ void CServerHandler::startGameplay()
 	switch(si->mode)
 	{
 	case StartInfo::NEW_GAME:
-		client->newGame();
+		client->newGame(gameState);
 		break;
 	case StartInfo::CAMPAIGN:
-		client->newGame();
+		client->newGame(gameState);
 		break;
 	case StartInfo::LOAD_GAME:
-		client->loadGame();
+		client->loadGame(gameState);
 		break;
 	default:
 		throw std::runtime_error("Invalid mode");
@@ -576,6 +586,9 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart)
 			GH.curInt = CMainMenu::create().get();
 		}
 	}
+	
+	c->enterLobbyConnectionMode();
+	c->disableStackSendingByID();
 }
 
 void CServerHandler::startCampaignScenario(std::shared_ptr<CCampaignState> cs)

+ 4 - 1
client/CServerHandler.h

@@ -21,6 +21,7 @@ class PlayerColor;
 struct StartInfo;
 
 class CMapInfo;
+class CGameState;
 struct ClientPlayer;
 struct CPack;
 struct CPackForLobby;
@@ -69,6 +70,7 @@ public:
 	virtual void sendMessage(const std::string & txt) const = 0;
 	virtual void sendGuiAction(ui8 action) const = 0; // TODO: possibly get rid of it?
 	virtual void sendStartGame(bool allowOnlyAI = false) const = 0;
+	virtual void sendRestartGame() const = 0;
 };
 
 /// structure to handle running server and connecting to it
@@ -142,9 +144,10 @@ public:
 	void setTurnLength(int npos) const override;
 	void sendMessage(const std::string & txt) const override;
 	void sendGuiAction(ui8 action) const override;
+	void sendRestartGame() const override;
 	void sendStartGame(bool allowOnlyAI = false) const override;
 
-	void startGameplay();
+	void startGameplay(CGameState * gameState = nullptr);
 	void endGameplay(bool closeConnection = true, bool restart = false);
 	void startCampaignScenario(std::shared_ptr<CCampaignState> cs = {});
 	void showServerError(std::string txt);

+ 45 - 33
client/Client.cpp

@@ -180,14 +180,15 @@ events::EventBus * CClient::eventBus() const
 	return clientEventBus.get();
 }
 
-void CClient::newGame()
+void CClient::newGame(CGameState * initializedGameState)
 {
 	CSH->th->update();
 	CMapService mapService;
-	gs = new CGameState();
+	gs = initializedGameState ? initializedGameState : new CGameState();
 	gs->preInit(VLC);
 	logNetwork->trace("\tCreating gamestate: %i", CSH->th->getDiff());
-	gs->init(&mapService, CSH->si.get(), settings["general"]["saveRandomMaps"].Bool());
+	if(!initializedGameState)
+		gs->init(&mapService, CSH->si.get(), settings["general"]["saveRandomMaps"].Bool());
 	logNetwork->trace("Initializing GameState (together): %d ms", CSH->th->getDiff());
 
 	initMapHandler();
@@ -196,53 +197,64 @@ void CClient::newGame()
 	initPlayerInterfaces();
 }
 
-void CClient::loadGame()
+void CClient::loadGame(CGameState * initializedGameState)
 {
 	logNetwork->info("Loading procedure started!");
-
+	
 	std::unique_ptr<CLoadFile> loader;
-	try
-	{
-		boost::filesystem::path clientSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(CSH->si->mapname, EResType::CLIENT_SAVEGAME));
-		boost::filesystem::path controlServerSaveName;
 
-		if(CResourceHandler::get("local")->existsResource(ResourceID(CSH->si->mapname, EResType::SERVER_SAVEGAME)))
-		{
-			controlServerSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(CSH->si->mapname, EResType::SERVER_SAVEGAME));
-		}
-		else // create entry for server savegame. Triggered if save was made after launch and not yet present in res handler
+	if(initializedGameState)
+	{
+		logNetwork->info("Game state was transferred over network, loading.");
+		gs = initializedGameState;
+	}
+	else
+	{
+		try
 		{
-			controlServerSaveName = boost::filesystem::path(clientSaveName).replace_extension(".vsgm1");
-			CResourceHandler::get("local")->createResource(controlServerSaveName.string(), true);
-		}
+			boost::filesystem::path clientSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(CSH->si->mapname, EResType::CLIENT_SAVEGAME));
+			boost::filesystem::path controlServerSaveName;
+
+			if(CResourceHandler::get("local")->existsResource(ResourceID(CSH->si->mapname, EResType::SERVER_SAVEGAME)))
+			{
+				controlServerSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(CSH->si->mapname, EResType::SERVER_SAVEGAME));
+			}
+			else // create entry for server savegame. Triggered if save was made after launch and not yet present in res handler
+			{
+				controlServerSaveName = boost::filesystem::path(clientSaveName).replace_extension(".vsgm1");
+				CResourceHandler::get("local")->createResource(controlServerSaveName.string(), true);
+			}
 
-		if(clientSaveName.empty())
-			throw std::runtime_error("Cannot open client part of " + CSH->si->mapname);
-		if(controlServerSaveName.empty() || !boost::filesystem::exists(controlServerSaveName))
-			throw std::runtime_error("Cannot open server part of " + CSH->si->mapname);
+			if(clientSaveName.empty())
+				throw std::runtime_error("Cannot open client part of " + CSH->si->mapname);
+			if(controlServerSaveName.empty() || !boost::filesystem::exists(controlServerSaveName))
+				throw std::runtime_error("Cannot open server part of " + CSH->si->mapname);
 
+			{
+				CLoadIntegrityValidator checkingLoader(clientSaveName, controlServerSaveName, MINIMAL_SERIALIZATION_VERSION);
+				loadCommonState(checkingLoader);
+				loader = checkingLoader.decay();
+			}
+		}
+		catch(std::exception & e)
 		{
-			CLoadIntegrityValidator checkingLoader(clientSaveName, controlServerSaveName, MINIMAL_SERIALIZATION_VERSION);
-			loadCommonState(checkingLoader);
-			loader = checkingLoader.decay();
+			logGlobal->error("Cannot load game %s. Error: %s", CSH->si->mapname, e.what());
+			throw; //obviously we cannot continue here
 		}
-
+		logNetwork->trace("Loaded common part of save %d ms", CSH->th->getDiff());
 	}
-	catch(std::exception & e)
-	{
-		logGlobal->error("Cannot load game %s. Error: %s", CSH->si->mapname, e.what());
-		throw; //obviously we cannot continue here
-	}
-	logNetwork->trace("Loaded common part of save %d ms", CSH->th->getDiff());
 	gs->preInit(VLC);
 	gs->updateOnLoad(CSH->si.get());
+	logNetwork->info("Game loaded, initialize interfaces.");
+	
 	initMapHandler();
 
 	reinitScripting();
 
 	initPlayerEnvironments();
-
-	serialize(loader->serializer, loader->serializer.fileVersion);
+	
+	if(loader)
+		serialize(loader->serializer, loader->serializer.fileVersion);
 
 	initPlayerInterfaces();
 }

+ 2 - 2
client/Client.h

@@ -150,8 +150,8 @@ public:
 	vstd::CLoggerBase * logger() const override;
 	events::EventBus * eventBus() const override;
 
-	void newGame();
-	void loadGame();
+	void newGame(CGameState * gameState);
+	void loadGame(CGameState * gameState);
 	void serialize(BinarySerializer & h, const int version);
 	void serialize(BinaryDeserializer & h, const int version);
 

+ 15 - 5
client/NetPacksLobbyClient.cpp

@@ -57,7 +57,8 @@ bool LobbyClientDisconnected::applyOnLobbyHandler(CServerHandler * handler)
 
 void LobbyClientDisconnected::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
 {
-	GH.popInts(1);
+	if(GH.listInt.size())
+		GH.popInts(1);
 }
 
 void LobbyChatMessage::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
@@ -93,25 +94,34 @@ void LobbyGuiAction::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * h
 	}
 }
 
-bool LobbyStartGame::applyOnLobbyHandler(CServerHandler * handler)
+bool LobbyEndGame::applyOnLobbyHandler(CServerHandler * handler)
 {
 	if(handler->state == EClientState::GAMEPLAY)
 	{
-		handler->endGameplay(false, true);
+		handler->endGameplay(closeConnection, restart);
 	}
+	
+	if(restart)
+		handler->sendStartGame();
+	
+	return true;
+}
+
+bool LobbyStartGame::applyOnLobbyHandler(CServerHandler * handler)
+{
 	handler->state = EClientState::STARTING;
 	if(handler->si->mode != StartInfo::LOAD_GAME)
 	{
 		handler->si = initializedStartInfo;
 	}
 	if(settings["session"]["headless"].Bool())
-		handler->startGameplay();
+		handler->startGameplay(initializedGameState);
 	return true;
 }
 
 void LobbyStartGame::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
 {
-	GH.pushIntT<CLoadingScreen>(std::bind(&CServerHandler::startGameplay, handler));
+	GH.pushIntT<CLoadingScreen>(std::bind(&CServerHandler::startGameplay, handler, initializedGameState));
 }
 
 bool LobbyUpdateState::applyOnLobbyHandler(CServerHandler * handler)

+ 0 - 3
lib/CCreatureSet.cpp

@@ -859,9 +859,6 @@ PlayerColor CStackInstance::getOwner() const
 
 void CStackInstance::deserializationFix()
 {
-	const CCreature *backup = type;
-	type = nullptr;
-		setType(backup);
 	const CArmedInstance *armyBackup = _armyObj;
 	_armyObj = nullptr;
 	setArmyObj(armyBackup);

+ 15 - 1
lib/CCreatureSet.h

@@ -12,6 +12,7 @@
 #include "HeroBonus.h"
 #include "GameConstants.h"
 #include "CArtHandler.h"
+#include "CCreatureHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -40,7 +41,20 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & type;
+		if(h.saving)
+		{
+			CreatureID idNumber = type ? type->idNumber : CreatureID(CreatureID::NONE);
+			h & idNumber;
+		}
+		else
+		{
+			CreatureID idNumber;
+			h & idNumber;
+			if(idNumber != CreatureID::NONE)
+				setType(VLC->creh->objects[idNumber]);
+			else
+				type = nullptr;
+		}
 		h & count;
 	}
 

+ 22 - 1
lib/NetPacksLobby.h

@@ -138,12 +138,29 @@ struct LobbyGuiAction : public CLobbyPackToPropagate
 	}
 };
 
+struct LobbyEndGame : public CLobbyPackToPropagate
+{
+	bool closeConnection = false, restart = false;
+	
+	bool checkClientPermissions(CVCMIServer * srv) const;
+	bool applyOnServer(CVCMIServer * srv);
+	void applyOnServerAfterAnnounce(CVCMIServer * srv);
+	bool applyOnLobbyHandler(CServerHandler * handler);
+	
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & closeConnection;
+		h & restart;
+	}
+};
+
 struct LobbyStartGame : public CLobbyPackToPropagate
 {
 	// Set by server
 	std::shared_ptr<StartInfo> initializedStartInfo;
+	CGameState * initializedGameState;
 
-	LobbyStartGame() : initializedStartInfo(nullptr) {}
+	LobbyStartGame() : initializedStartInfo(nullptr), initializedGameState(nullptr) {}
 	bool checkClientPermissions(CVCMIServer * srv) const;
 	bool applyOnServer(CVCMIServer * srv);
 	void applyOnServerAfterAnnounce(CVCMIServer * srv);
@@ -153,6 +170,10 @@ struct LobbyStartGame : public CLobbyPackToPropagate
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & initializedStartInfo;
+		bool sps = h.smartPointerSerialization;
+		h.smartPointerSerialization = true;
+		h & initializedGameState;
+		h.smartPointerSerialization = sps;
 	}
 };
 

+ 2 - 1
lib/mapping/CMap.h

@@ -330,7 +330,7 @@ public:
 		h & players;
 		h & howManyTeams;
 		h & allowedHeroes;
-		h & triggeredEvents;
+		//Do not serialize triggeredEvents in header as they can contain information about heroes and armies
 		h & victoryMessage;
 		h & victoryIconIndex;
 		h & defeatMessage;
@@ -424,6 +424,7 @@ public:
 	void serialize(Handler &h, const int formatVersion)
 	{
 		h & static_cast<CMapHeader&>(*this);
+		h & triggeredEvents; //from CMapHeader
 		h & rumors;
 		h & allowedSpell;
 		h & allowedAbilities;

+ 1 - 0
lib/registerTypes/RegisterTypes.h

@@ -372,6 +372,7 @@ void registerTypesLobbyPacks(Serializer &s)
 	s.template registerType<CLobbyPackToPropagate, LobbyChatMessage>();
 	// Only host client send
 	s.template registerType<CLobbyPackToPropagate, LobbyGuiAction>();
+	s.template registerType<CLobbyPackToPropagate, LobbyEndGame>();
 	s.template registerType<CLobbyPackToPropagate, LobbyStartGame>();
 	s.template registerType<CLobbyPackToPropagate, LobbyChangeHost>();
 	// Only server send

+ 13 - 3
server/CVCMIServer.cpp

@@ -220,7 +220,7 @@ void CVCMIServer::threadAnnounceLobby()
 	}
 }
 
-bool CVCMIServer::prepareToStartGame()
+void CVCMIServer::prepareToRestart()
 {
 	if(state == EServerState::GAMEPLAY)
 	{
@@ -231,9 +231,19 @@ bool CVCMIServer::prepareToStartGame()
 		// FIXME: dirry hack to make sure old CGameHandler::run is finished
 		boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
 	}
+	
+	for(auto c : connections)
+	{
+		c->enterLobbyConnectionMode();
+		c->disableStackSendingByID();
+	}
+	boost::unique_lock<boost::recursive_mutex> queueLock(mx);
+	gh = nullptr;
+}
 
-	if(!gh)
-		gh = std::make_shared<CGameHandler>(this);
+bool CVCMIServer::prepareToStartGame()
+{
+	gh = std::make_shared<CGameHandler>(this);
 	switch(si->mode)
 	{
 	case StartInfo::CAMPAIGN:

+ 1 - 0
server/CVCMIServer.h

@@ -69,6 +69,7 @@ public:
 	~CVCMIServer();
 	void run();
 	bool prepareToStartGame();
+	void prepareToRestart();
 	void startGameImmidiately();
 
 	void startAsyncAccept();

+ 23 - 0
server/NetPacksLobbyServer.cpp

@@ -161,6 +161,27 @@ bool LobbyGuiAction::checkClientPermissions(CVCMIServer * srv) const
 	return srv->isClientHost(c->connectionID);
 }
 
+bool LobbyEndGame::checkClientPermissions(CVCMIServer * srv) const
+{
+	return srv->isClientHost(c->connectionID);
+}
+
+bool LobbyEndGame::applyOnServer(CVCMIServer * srv)
+{
+	srv->prepareToRestart();
+	return true;
+}
+
+void LobbyEndGame::applyOnServerAfterAnnounce(CVCMIServer * srv)
+{
+	boost::unique_lock<boost::mutex> stateLock(srv->stateMutex);
+	for(auto & c : srv->connections)
+	{
+		c->enterLobbyConnectionMode();
+		c->disableStackSendingByID();
+	}
+}
+
 bool LobbyStartGame::checkClientPermissions(CVCMIServer * srv) const
 {
 	return srv->isClientHost(c->connectionID);
@@ -181,6 +202,8 @@ bool LobbyStartGame::applyOnServer(CVCMIServer * srv)
 		return false;
 	
 	initializedStartInfo = std::make_shared<StartInfo>(*srv->gh->getStartInfo(true));
+	initializedGameState = srv->gh->gameState();
+
 	return true;
 }