Browse Source

Extracted interface through which GameHandler interacts with VCMIServer

Ivan Savenko 3 months ago
parent
commit
3d63284c51

+ 1 - 1
client/Client.cpp

@@ -144,7 +144,7 @@ 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();

+ 6 - 6
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)

+ 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;

+ 38 - 95
server/CGameHandler.cpp

@@ -128,9 +128,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)
@@ -439,38 +439,36 @@ void CGameHandler::changeSecSkill(const CGHeroInstance * hero, SecondarySkill wh
 
 void CGameHandler::handleClientDisconnection(const std::shared_ptr<CConnection> & c)
 {
-	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, c))
+			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);
 		}
 	}
@@ -526,8 +524,8 @@ void CGameHandler::handleReceivedPack(std::shared_ptr<CConnection> connection, C
 	}
 }
 
-CGameHandler::CGameHandler(CVCMIServer * lobby)
-	: lobby(lobby)
+CGameHandler::CGameHandler(IGameServer & server)
+	: server(server)
 	, heroPool(std::make_unique<HeroPoolProcessor>(this))
 	, battles(std::make_unique<BattleProcessor>(this))
 	, queries(std::make_unique<QueriesProcessor>())
@@ -741,19 +739,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
@@ -1516,8 +1501,7 @@ 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);
+	gameServer().broadcastPack(pack);
 }
 
 void CGameHandler::sendAndApply(CPackForClient & pack)
@@ -1625,64 +1609,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)
@@ -2097,12 +2040,12 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8
 
 bool CGameHandler::hasPlayerAt(PlayerColor player,  const std::shared_ptr<CConnection> & c) const
 {
-	return connections.count(player) && connections.at(player).count(c);
+	return gameServer().hasPlayerAt(player, c);
 }
 
 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)
@@ -3589,7 +3532,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
 
 			if(p->human)
 			{
-				gameLobby().setState(EServerState::SHUTDOWN);
+				gameServer().setState(EServerState::SHUTDOWN);
 			}
 		}
 		else

+ 5 - 6
server/CGameHandler.h

@@ -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,7 @@ 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();
 
 	//////////////////////////////////////////////////////////////////////////
@@ -237,7 +236,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);

+ 1 - 0
server/CMakeLists.txt

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

+ 89 - 2
server/CVCMIServer.cpp

@@ -23,6 +23,7 @@
 #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"
@@ -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 " << 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();
@@ -1077,3 +1089,78 @@ 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, const std::shared_ptr<CConnection> & c) const
+{
+	return vstd::contains(getAllClientPlayers(c->connectionID), player);
+}
+
+bool CVCMIServer::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const
+{
+	for (const auto & c : activeConnections)
+	{
+		if (hasPlayerAt(left, c) && hasPlayerAt(right, c))
+			return true;
+	}
+
+	return false;
+}
+
+void CVCMIServer::broadcastPack(const CPackForClient & pack)
+{
+	for (const auto & c : activeConnections)
+		c->sendPack(pack);
+}
+

+ 14 - 11
server/CVCMIServer.h

@@ -9,6 +9,8 @@
  */
 #pragma once
 
+#include "IGameServer.h"
+
 #include "../lib/network/NetworkInterface.h"
 #include "../lib/StartInfo.h"
 
@@ -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;
@@ -59,7 +54,18 @@ class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INet
 	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, const std::shared_ptr<CConnection> & c) const override;
+	bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const override;
+	void broadcastPack(const CPackForClient & pack) override;
+
 	/// List of all active connections
 	std::vector<std::shared_ptr<CConnection>> activeConnections;
 
@@ -109,9 +115,6 @@ public:
 	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);

+ 37 - 0
server/IGameServer.h

@@ -0,0 +1,37 @@
+/*
+ * 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
+
+VCMI_LIB_NAMESPACE_BEGIN
+class PlayerColor;
+class CConnection;
+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, const std::shared_ptr<CConnection> & c) const = 0;
+	virtual bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const = 0;
+	virtual void broadcastPack(const CPackForClient & pack) = 0;
+};

+ 5 - 5
server/processors/PlayerMessageProcessor.cpp

@@ -70,17 +70,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 +112,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 +147,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;
 

+ 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;
 	}