Browse Source

Moved management of turn order into a new class

Ivan Savenko 2 years ago
parent
commit
c4bc6840ea

+ 1 - 1
include/vcmi/events/PlayerGotTurn.h

@@ -29,7 +29,7 @@ public:
 	using ExecHandler = Sub::ExecHandler;
 
 	static Sub * getRegistry();
-	static void defaultExecute(const EventBus * bus, const ExecHandler & execHandler, PlayerColor & player);
+	static void defaultExecute(const EventBus * bus, PlayerColor & player);
 
 	virtual PlayerColor getPlayer() const = 0;
 	virtual void setPlayer(const PlayerColor & value) = 0;

+ 2 - 2
lib/events/PlayerGotTurn.cpp

@@ -24,11 +24,11 @@ SubscriptionRegistry<PlayerGotTurn> * PlayerGotTurn::getRegistry()
 	return Instance.get();
 }
 
-void PlayerGotTurn::defaultExecute(const EventBus * bus, const ExecHandler & execHandler, PlayerColor & player)
+void PlayerGotTurn::defaultExecute(const EventBus * bus, PlayerColor & player)
 {
 	CPlayerGotTurn event;
 	event.setPlayer(player);
-	bus->executeEvent(event, execHandler);
+	bus->executeEvent(event);
 	player = event.getPlayer();
 }
 

+ 39 - 165
server/CGameHandler.cpp

@@ -16,6 +16,7 @@
 #include "battles/BattleProcessor.h"
 #include "processors/HeroPoolProcessor.h"
 #include "processors/PlayerMessageProcessor.h"
+#include "processors/TurnOrderProcessor.h"
 #include "queries/QueriesProcessor.h"
 #include "queries/MapQueries.h"
 
@@ -92,7 +93,7 @@ public:
 		T *ptr = static_cast<T*>(pack);
 		try
 		{
-			ApplyGhNetPackVisitor applier(*gh, *gs);
+			ApplyGhNetPackVisitor applier(*gh);
 
 			ptr->visit(applier);
 
@@ -123,51 +124,6 @@ static inline double distance(int3 a, int3 b)
 	return std::sqrt((double)(a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
 }
 
-PlayerStatus PlayerStatuses::operator[](PlayerColor player)
-{
-	boost::unique_lock<boost::mutex> l(mx);
-	if (players.find(player) != players.end())
-	{
-		return players.at(player);
-	}
-	else
-	{
-		throw std::runtime_error("No such player!");
-	}
-}
-void PlayerStatuses::addPlayer(PlayerColor player)
-{
-	boost::unique_lock<boost::mutex> l(mx);
-	players[player];
-}
-
-bool PlayerStatuses::checkFlag(PlayerColor player, bool PlayerStatus::*flag)
-{
-	boost::unique_lock<boost::mutex> l(mx);
-	if (players.find(player) != players.end())
-	{
-		return players[player].*flag;
-	}
-	else
-	{
-		throw std::runtime_error("No such player!");
-	}
-}
-
-void PlayerStatuses::setFlag(PlayerColor player, bool PlayerStatus::*flag, bool val)
-{
-	boost::unique_lock<boost::mutex> l(mx);
-	if (players.find(player) != players.end())
-	{
-		players[player].*flag = val;
-	}
-	else
-	{
-		throw std::runtime_error("No such player!");
-	}
-	cv.notify_all();
-}
-
 template <typename T>
 void callWith(std::vector<T> args, std::function<void(T)> fun, ui32 which)
 {
@@ -480,7 +436,7 @@ void CGameHandler::changeSecSkill(const CGHeroInstance * hero, SecondarySkill wh
 
 void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
 {
-	if(lobby->state == EServerState::SHUTDOWN || !gs || !gs->scenarioOps)
+	if(lobby->getState() == EServerState::SHUTDOWN || !gs || !gs->scenarioOps)
 		return;
 	
 	for(auto & playerConnections : connections)
@@ -546,6 +502,7 @@ CGameHandler::CGameHandler(CVCMIServer * lobby)
 	: lobby(lobby)
 	, heroPool(std::make_unique<HeroPoolProcessor>(this))
 	, battles(std::make_unique<BattleProcessor>(this))
+	, turnOrder(std::make_unique<TurnOrderProcessor>(this))
 	, queries(std::make_unique<QueriesProcessor>())
 	, playerMessages(std::make_unique<PlayerMessageProcessor>(this))
 	, complainNoCreatures("No creatures to split")
@@ -592,9 +549,7 @@ void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTrack
 	getRandomGenerator().resetSeed();
 
 	for (auto & elem : gs->players)
-	{
-		states.addPlayer(elem.first);
-	}
+		turnOrder->addPlayer(elem.first);
 
 	reinitScripting();
 }
@@ -646,7 +601,18 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa
 		}
 }
 
-void CGameHandler::newTurn()
+void CGameHandler::onPlayerTurnStarted(PlayerColor which)
+{
+	events::PlayerGotTurn::defaultExecute(serverEventBus.get(), which);
+	turnTimerHandler.onPlayerGetTurn(gs->players[which]);
+}
+
+void CGameHandler::onPlayerTurnEnded(PlayerColor which)
+{
+
+}
+
+void CGameHandler::onNewTurn()
 {
 	logGlobal->trace("Turn %d", gs->day+1);
 	NewTurn n;
@@ -965,6 +931,7 @@ void CGameHandler::newTurn()
 
 	synchronizeArtifactHandlerLists(); //new day events may have changed them. TODO better of managing that
 }
+
 void CGameHandler::run(bool resume)
 {
 	LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume);
@@ -989,112 +956,29 @@ void CGameHandler::run(bool resume)
 	services()->scripts()->run(serverScripts);
 #endif
 
-	if(resume)
-		events::GameResumed::defaultExecute(serverEventBus.get());
-
-	auto playerTurnOrder = generatePlayerTurnOrder();
-	
-	if(!resume)
-		for(auto & playerColor : playerTurnOrder)
-			turnTimerHandler.onGameplayStart(gs->players[playerColor]);
-	
-	while(lobby->state == EServerState::GAMEPLAY)
+	if (!resume)
 	{
-		if(!resume)
-		{
-			newTurn();
-			events::TurnStarted::defaultExecute(serverEventBus.get());
-		}
-
-		std::list<PlayerColor>::iterator it;
-		if (resume)
-		{
-			it = std::find(playerTurnOrder.begin(), playerTurnOrder.end(), gs->currentPlayer);
-		}
-		else
-		{
-			it = playerTurnOrder.begin();
-		}
-
-		resume = false;
-		for (; (it != playerTurnOrder.end()) && (lobby->state == EServerState::GAMEPLAY) ; it++)
-		{
-			auto playerColor = *it;
-
-			auto onGetTurn = [&](events::PlayerGotTurn & event)
-			{
-				//if player runs out of time, he shouldn't get the turn (especially AI)
-				//pre-trigger may change anything, should check before each player
-				//TODO: is it enough to check only one player?
-				checkVictoryLossConditionsForAll();
-
-				auto player = event.getPlayer();
-
-				const PlayerState * playerState = &gs->players[player];
-
-				if(playerState->status != EPlayerStatus::INGAME)
-				{
-					event.setPlayer(PlayerColor::CANNOT_DETERMINE);
-				}
-				else
-				{
-					states.setFlag(player, &PlayerStatus::makingTurn, true);
-
-					YourTurn yt;
-					yt.player = player;
-					//Change local daysWithoutCastle counter for local interface message //TODO: needed?
-					yt.daysWithoutCastle = playerState->daysWithoutCastle;
-					applyAndSend(&yt);
-					
-					turnTimerHandler.onPlayerGetTurn(gs->players[player]);
-				}
-			};
-
-			events::PlayerGotTurn::defaultExecute(serverEventBus.get(), onGetTurn, playerColor);
-
-			if(playerColor != PlayerColor::CANNOT_DETERMINE)
-			{
-				//wait till turn is done
-				const int waitTime = 100; //ms
-				boost::unique_lock<boost::mutex> lock(states.mx);
-				while(states.players.at(playerColor).makingTurn && lobby->state == EServerState::GAMEPLAY)
-				{
-					turnTimerHandler.onPlayerMakingTurn(gs->players[playerColor], waitTime);
-					if(gs->curB)
-						turnTimerHandler.onBattleLoop(waitTime);
-
-					states.cv.wait_for(lock, boost::chrono::milliseconds(waitTime));
-				}
-			}
-		}
-		//additional check that game is not finished
-		bool activePlayer = false;
-		for (auto player : playerTurnOrder)
-		{
-			if (gs->players[player].status == EPlayerStatus::INGAME)
-					activePlayer = true;
-		}
-		if(!activePlayer)
-			lobby->state = EServerState::GAMEPLAY_ENDED;
+		onNewTurn();
+		events::TurnStarted::defaultExecute(serverEventBus.get());
+		for(auto & player : gs->players)
+			turnTimerHandler.onGameplayStart(player.second);
 	}
-}
+	else
+		events::GameResumed::defaultExecute(serverEventBus.get());
 
-std::list<PlayerColor> CGameHandler::generatePlayerTurnOrder() const
-{
-	// Generate player turn order
-	std::list<PlayerColor> playerTurnOrder;
+	turnOrder->onGameStarted();
 
-	for (const auto & player : gs->players) // add human players first
+	//wait till game is done
+	while(lobby->getState() == EServerState::GAMEPLAY)
 	{
-		if (player.second.human)
-			playerTurnOrder.push_back(player.first);
-	}
-	for (const auto & player : gs->players) // then add non-human players
-	{
-		if (!player.second.human)
-			playerTurnOrder.push_back(player.first);
+		const int waitTime = 100; //ms
+
+		turnTimerHandler.onPlayerMakingTurn(gs->players[gs->getCurrentPlayer()], waitTime);
+		if(gs->curB)
+			turnTimerHandler.onBattleLoop(waitTime);
+
+		boost::this_thread::sleep_for(boost::chrono::milliseconds(waitTime));
 	}
-	return playerTurnOrder;
 }
 
 void CGameHandler::giveSpells(const CGTownInstance *t, const CGHeroInstance *h)
@@ -3617,7 +3501,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
 
 			if(p->human)
 			{
-				lobby->state = EServerState::GAMEPLAY_ENDED;
+				lobby->setState(EServerState::GAMEPLAY_ENDED);
 			}
 		}
 		else
@@ -3662,12 +3546,11 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
 		}
 
 		auto playerInfo = getPlayerState(gs->currentPlayer, false);
+
 		// If we are called before the actual game start, there might be no current player
+		// If player making turn has lost his turn must be over as well
 		if (playerInfo && playerInfo->status != EPlayerStatus::INGAME)
-		{
-			// If player making turn has lost his turn must be over as well
-			states.setFlag(gs->currentPlayer, &PlayerStatus::makingTurn, false);
-		}
+			turnOrder->onPlayerEndsTurn(gs->currentPlayer);
 	}
 }
 
@@ -4229,15 +4112,6 @@ void CGameHandler::createObject(const int3 & visitablePosition, Obj type, int32_
 	sendAndApply(&no);
 }
 
-void CGameHandler::deserializationFix()
-{
-	//FIXME: pointer to GameHandler itself can't be deserialized at the moment since GameHandler is top-level entity in serialization
-	// restore any places that requires such pointer manually
-	heroPool->gameHandler = this;
-	battles->setGameHandler(this);
-	playerMessages->gameHandler = this;
-}
-
 void CGameHandler::startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank, const CGTownInstance *town)
 {
 	battles->startBattlePrimary(army1, army2, tile, hero1, hero2, creatureBank, town);

+ 10 - 40
server/CGameHandler.h

@@ -49,36 +49,10 @@ class CVCMIServer;
 class CBaseForGHApply;
 class PlayerMessageProcessor;
 class BattleProcessor;
+class TurnOrderProcessor;
 class QueriesProcessor;
 class CObjectVisitQuery;
 
-struct PlayerStatus
-{
-	bool makingTurn;
-
-	PlayerStatus():makingTurn(false){};
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & makingTurn;
-	}
-};
-class PlayerStatuses
-{
-public:
-	std::map<PlayerColor,PlayerStatus> players;
-	boost::mutex mx;
-	boost::condition_variable cv; //notifies when any changes are made
-
-	void addPlayer(PlayerColor player);
-	PlayerStatus operator[](PlayerColor player);
-	bool checkFlag(PlayerColor player, bool PlayerStatus::*flag);
-	void setFlag(PlayerColor player, bool PlayerStatus::*flag, bool val);
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & players;
-	}
-};
-
 class CGameHandler : public IGameCallback, public CBattleInfoCallback, public Environment
 {
 	CVCMIServer * lobby;
@@ -90,6 +64,7 @@ public:
 	std::unique_ptr<HeroPoolProcessor> heroPool;
 	std::unique_ptr<BattleProcessor> battles;
 	std::unique_ptr<QueriesProcessor> queries;
+	std::unique_ptr<TurnOrderProcessor> turnOrder;
 
 	//use enums as parameters, because doMove(sth, true, false, true) is not readable
 	enum EGuardLook {CHECK_FOR_GUARDS, IGNORE_GUARDS};
@@ -99,13 +74,11 @@ 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
-	PlayerStatuses states; //player color -> player state
 
 	//queries stuff
 	boost::recursive_mutex gsm;
 	ui32 QID;
 
-
 	SpellCastEnvironment * spellEnv;
 	
 	TurnTimerHandler turnTimerHandler;
@@ -237,6 +210,10 @@ public:
 	void save(const std::string &fname);
 	bool load(const std::string &fname);
 
+	void onPlayerTurnStarted(PlayerColor which);
+	void onPlayerTurnEnded(PlayerColor which);
+	void onNewTurn();
+
 	void handleTimeEvents();
 	void handleTownEvents(CGTownInstance *town, NewTurn &n);
 	bool complain(const std::string &problem); //sends message to all clients, prints on the logs and return true
@@ -248,14 +225,11 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & QID;
-		h & states;
-		h & battles;
-		h & heroPool;
 		h & getRandomGenerator();
-		h & playerMessages;
-
-		if (!h.saving)
-			deserializationFix();
+		h & *battles;
+		h & *heroPool;
+		h & *playerMessages;
+		h & *turnOrder;
 
 #if SCRIPTING_ENABLED
 		JsonNode scriptsState;
@@ -282,7 +256,6 @@ public:
 	bool isPlayerOwns(CPackForServer * pack, ObjectInstanceID id);
 
 	void run(bool resume);
-	void newTurn();
 	bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector<ArtifactPosition> & slot);
 	void spawnWanderingMonsters(CreatureID creatureID);
 
@@ -298,8 +271,6 @@ public:
 	scripting::Pool * getContextPool() const override;
 #endif
 
-	std::list<PlayerColor> generatePlayerTurnOrder() const;
-
 	friend class CVCMIServer;
 private:
 	std::unique_ptr<events::EventBus> serverEventBus;
@@ -308,7 +279,6 @@ private:
 #endif
 
 	void reinitScripting();
-	void deserializationFix();
 
 	void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const;
 

+ 2 - 0
server/CMakeLists.txt

@@ -13,6 +13,7 @@ set(server_SRCS
 
 		processors/HeroPoolProcessor.cpp
 		processors/PlayerMessageProcessor.cpp
+		processors/TurnOrderProcessor.cpp
 
 		CGameHandler.cpp
 		ServerSpellCastEnvironment.cpp
@@ -37,6 +38,7 @@ set(server_HEADERS
 
 		processors/HeroPoolProcessor.h
 		processors/PlayerMessageProcessor.h
+		processors/TurnOrderProcessor.h
 
 		CGameHandler.h
 		ServerSpellCastEnvironment.h

+ 12 - 2
server/CVCMIServer.cpp

@@ -158,6 +158,16 @@ CVCMIServer::~CVCMIServer()
 		announceLobbyThread->join();
 }
 
+void CVCMIServer::setState(EServerState value)
+{
+	state.store(value);
+}
+
+EServerState CVCMIServer::getState() const
+{
+	return state.load();
+}
+
 void CVCMIServer::run()
 {
 	if(!restartGameplay)
@@ -1159,7 +1169,7 @@ int main(int argc, const char * argv[])
 
 		try
 		{
-			while(server.state != EServerState::SHUTDOWN)
+			while(server.getState() != EServerState::SHUTDOWN)
 			{
 				server.run();
 			}
@@ -1168,7 +1178,7 @@ int main(int argc, const char * argv[])
 		catch(boost::system::system_error & e) //for boost errors just log, not crash - probably client shut down connection
 		{
 			logNetwork->error(e.what());
-			server.state = EServerState::SHUTDOWN;
+			server.setState(EServerState::SHUTDOWN);
 		}
 	}
 	catch(boost::system::system_error & e)

+ 4 - 1
server/CVCMIServer.h

@@ -56,10 +56,10 @@ class CVCMIServer : public LobbyInfo
 	boost::recursive_mutex mx;
 	std::shared_ptr<CApplier<CBaseForServerApply>> applier;
 	std::unique_ptr<boost::thread> announceLobbyThread, remoteConnectionsThread;
+	std::atomic<EServerState> state;
 
 public:
 	std::shared_ptr<CGameHandler> gh;
-	std::atomic<EServerState> state;
 	ui16 port;
 
 	boost::program_options::variables_map cmdLineOptions;
@@ -101,6 +101,9 @@ public:
 
 	void updateAndPropagateLobbyState();
 
+	void setState(EServerState value);
+	EServerState getState() const;
+
 	// Work with LobbyInfo
 	void setPlayer(PlayerColor clickedColor);
 	void optionNextHero(PlayerColor player, int dir); //dir == -1 or +1

+ 8 - 8
server/NetPacksLobbyServer.cpp

@@ -53,11 +53,11 @@ void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientConnected(LobbyClie
 		}
 	}
 	
-	if(srv.state == EServerState::LOBBY)
-		{
+	if(srv.getState() == EServerState::LOBBY)
+	{
 		result = true;
 		return;
-		}
+	}
 	
 	//disconnect immediately and ignore this client
 	srv.connections.erase(pack.c);
@@ -115,7 +115,7 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientConnected(LobbyCl
 	// Until UUID set we only pass LobbyClientConnected to this client
 	pack.c->uuid = pack.uuid;
 	srv.updateAndPropagateLobbyState();
-	if(srv.state == EServerState::GAMEPLAY)
+	if(srv.getState() == EServerState::GAMEPLAY)
 	{
 		//immediately start game
 		std::unique_ptr<LobbyStartGame> startGameForReconnectedPlayer(new LobbyStartGame);
@@ -173,13 +173,13 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientDisconnected(Lobb
 	if(pack.shutdownServer)
 	{
 		logNetwork->info("Client requested shutdown, server will close itself...");
-		srv.state = EServerState::SHUTDOWN;
+		srv.setState(EServerState::SHUTDOWN);
 		return;
 	}
 	else if(srv.connections.empty())
 	{
 		logNetwork->error("Last connection lost, server will close itself...");
-		srv.state = EServerState::SHUTDOWN;
+		srv.setState(EServerState::SHUTDOWN);
 	}
 	else if(pack.c == srv.hostClient)
 	{
@@ -198,7 +198,7 @@ void ClientPermissionsCheckerNetPackVisitor::visitLobbyChatMessage(LobbyChatMess
 
 void ApplyOnServerNetPackVisitor::visitLobbySetMap(LobbySetMap & pack)
 {
-	if(srv.state != EServerState::LOBBY)
+	if(srv.getState() != EServerState::LOBBY)
 	{
 		result = false;
 		return;
@@ -300,7 +300,7 @@ void ApplyOnServerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
 	pack.initializedStartInfo = std::make_shared<StartInfo>(*srv.gh->getStartInfo(true));
 	pack.initializedGameState = srv.gh->gameState();
 
-	srv.state = EServerState::GAMEPLAY_STARTING;
+	srv.setState(EServerState::GAMEPLAY_STARTING);
 	result = true;
 }
 

+ 4 - 19
server/NetPacksServer.cpp

@@ -14,6 +14,7 @@
 #include "battles/BattleProcessor.h"
 #include "processors/HeroPoolProcessor.h"
 #include "processors/PlayerMessageProcessor.h"
+#include "processors/TurnOrderProcessor.h"
 #include "queries/QueriesProcessor.h"
 
 #include "../lib/IGameCallback.h"
@@ -36,24 +37,10 @@ void ApplyGhNetPackVisitor::visitSaveGame(SaveGame & pack)
 
 void ApplyGhNetPackVisitor::visitEndTurn(EndTurn & pack)
 {
-	PlayerColor currentPlayer = gs.currentPlayer;
-	if(pack.player != currentPlayer)
-	{
-		if(gh.getPlayerStatus(pack.player) == EPlayerStatus::INGAME)
-			gh.throwAndComplain(&pack, "pack.player attempted to end turn for another pack.player!");
-
-		logGlobal->debug("pack.player attempted to end turn after game over. Ignoring this request.");
-
-		result = true;
-		return;
-	}
-
-	gh.throwOnWrongPlayer(&pack, pack.player);
-	if(gh.queries->topQuery(pack.player))
-		gh.throwAndComplain(&pack, "Cannot end turn before resolving queries!");
+	if (!gh.hasPlayerAt(pack.player, pack.c))
+		gh.throwAndComplain(&pack, "No such pack.player!");
 
-	gh.states.setFlag(gs.currentPlayer, &PlayerStatus::makingTurn, false);
-	result = true;
+	result = gh.turnOrder->onPlayerEndsTurn(pack.player);
 }
 
 void ApplyGhNetPackVisitor::visitDismissHero(DismissHero & pack)
@@ -274,8 +261,6 @@ void ApplyGhNetPackVisitor::visitQueryReply(QueryReply & pack)
 	if(pack.qid == QueryID(-1))
 		gh.throwAndComplain(&pack, "Cannot answer the query with pack.id -1!");
 
-	assert(vstd::contains(gh.states.players, pack.player));
-
 	result = gh.queryReply(pack.qid, pack.reply, pack.player);
 }
 

+ 2 - 3
server/ServerNetPackVisitors.h

@@ -16,11 +16,10 @@ class ApplyGhNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
 private:
 	bool result;
 	CGameHandler & gh;
-	CGameState & gs;
 
 public:
-	ApplyGhNetPackVisitor(CGameHandler & gh, CGameState & gs)
-		:gh(gh), gs(gs), result(false)
+	ApplyGhNetPackVisitor(CGameHandler & gh)
+		:gh(gh), result(false)
 	{
 	}
 

+ 3 - 2
server/TurnTimerHandler.cpp

@@ -12,6 +12,7 @@
 #include "CGameHandler.h"
 #include "battles/BattleProcessor.h"
 #include "queries/QueriesProcessor.h"
+#include "processors/TurnOrderProcessor.h"
 #include "../lib/battle/BattleInfo.h"
 #include "../lib/gameState/CGameState.h"
 #include "../lib/CPlayerState.h"
@@ -83,8 +84,8 @@ void TurnTimerHandler::onPlayerMakingTurn(PlayerState & state, int waitTime)
 			state.turnTimer.baseTimer = 0;
 			onPlayerMakingTurn(state, waitTime);
 		}
-		else if(!gameHandler.queries->topQuery(state.color)) //wait for replies to avoid pending queries
-			gameHandler.states.players.at(state.color).makingTurn = false; //force end turn
+		else if(!gameHandler.queries->topQuery(state.color))
+			gameHandler.turnOrder->onPlayerEndsTurn(state.color);
 	}
 }
 

+ 0 - 1
server/battles/BattleFlowProcessor.cpp

@@ -286,7 +286,6 @@ const CStack * BattleFlowProcessor::getNextStack()
 
 void BattleFlowProcessor::activateNextStack()
 {
-	//TODO: activate next round if next == nullptr
 	const auto & curB = *gameHandler->gameState()->curB;
 
 	// Find next stack that requires manual control

+ 3 - 25
server/processors/HeroPoolProcessor.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 #include "HeroPoolProcessor.h"
 
+#include "TurnOrderProcessor.h"
 #include "../CGameHandler.h"
 
 #include "../../lib/CHeroHandler.h"
@@ -32,29 +33,6 @@ HeroPoolProcessor::HeroPoolProcessor(CGameHandler * gameHandler)
 {
 }
 
-bool HeroPoolProcessor::playerEndedTurn(const PlayerColor & player)
-{
-	// our player is acting right now and have not ended turn
-	if (player == gameHandler->gameState()->currentPlayer)
-		return false;
-
-	auto turnOrder = gameHandler->generatePlayerTurnOrder();
-
-	for (auto const & entry : turnOrder)
-	{
-		// our player is yet to start turn
-		if (entry == gameHandler->gameState()->currentPlayer)
-			return false;
-
-		// our player have finished turn
-		if (entry == player)
-			return true;
-	}
-
-	assert(false);
-	return false;
-}
-
 TavernHeroSlot HeroPoolProcessor::selectSlotForRole(const PlayerColor & player, TavernSlotRole roleID)
 {
 	const auto & heroesPool = gameHandler->gameState()->heroesPool;
@@ -90,7 +68,7 @@ TavernHeroSlot HeroPoolProcessor::selectSlotForRole(const PlayerColor & player,
 void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHeroInstance * hero)
 {
 	SetAvailableHero sah;
-	if (playerEndedTurn(color))
+	if (gameHandler->turnOrder->playerAwaitsNewDay(color))
 		sah.roleID = TavernSlotRole::SURRENDERED_TODAY;
 	else
 		sah.roleID = TavernSlotRole::SURRENDERED;
@@ -104,7 +82,7 @@ void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHer
 void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero)
 {
 	SetAvailableHero sah;
-	if (playerEndedTurn(color))
+	if (gameHandler->turnOrder->playerAwaitsNewDay(color))
 		sah.roleID = TavernSlotRole::RETREATED_TODAY;
 	else
 		sah.roleID = TavernSlotRole::RETREATED;

+ 0 - 1
server/processors/HeroPoolProcessor.h

@@ -43,7 +43,6 @@ class HeroPoolProcessor : boost::noncopyable
 
 	TavernHeroSlot selectSlotForRole(const PlayerColor & player, TavernSlotRole roleID);
 
-	bool playerEndedTurn(const PlayerColor & player);
 public:
 	CGameHandler * gameHandler;
 

+ 1 - 1
server/processors/PlayerMessageProcessor.cpp

@@ -71,7 +71,7 @@ bool PlayerMessageProcessor::handleHostCommand(PlayerColor player, const std::st
 	if(words[1] == "exit" || words[1] == "quit" || words[1] == "end")
 	{
 		broadcastSystemMessage("game was terminated");
-		gameHandler->gameLobby()->state = EServerState::SHUTDOWN;
+		gameHandler->gameLobby()->setState(EServerState::SHUTDOWN);
 
 		return true;
 	}

+ 183 - 0
server/processors/TurnOrderProcessor.cpp

@@ -0,0 +1,183 @@
+/*
+ * TurnOrderProcessor.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 "TurnOrderProcessor.h"
+
+#include "../queries/QueriesProcessor.h"
+#include "../CGameHandler.h"
+#include "../CVCMIServer.h"
+
+#include "../../lib/CPlayerState.h"
+#include "../../lib/NetPacks.h"
+
+TurnOrderProcessor::TurnOrderProcessor(CGameHandler * owner):
+	gameHandler(owner)
+{
+
+}
+
+bool TurnOrderProcessor::canActSimultaneously(PlayerColor active, PlayerColor waiting) const
+{
+	return false;
+}
+
+bool TurnOrderProcessor::mustActBefore(PlayerColor left, PlayerColor right) const
+{
+	const auto * leftInfo = gameHandler->getPlayerState(left, false);
+	const auto * rightInfo = gameHandler->getPlayerState(right, false);
+
+	assert(left != right);
+	assert(leftInfo && rightInfo);
+
+	if (!leftInfo)
+		return false;
+	if (!rightInfo)
+		return true;
+
+	if (leftInfo->isHuman() && !rightInfo->isHuman())
+		return true;
+
+	if (!leftInfo->isHuman() && rightInfo->isHuman())
+		return false;
+
+	return left < right;
+}
+
+bool TurnOrderProcessor::canStartTurn(PlayerColor which) const
+{
+	for (auto player : awaitingPlayers)
+	{
+		if (mustActBefore(player, which))
+			return false;
+	}
+
+	for (auto player : actingPlayers)
+	{
+		if (!canActSimultaneously(player, which))
+			return false;
+	}
+
+	return true;
+}
+
+void TurnOrderProcessor::doStartNewDay()
+{
+	assert(awaitingPlayers.empty());
+	assert(actingPlayers.empty());
+
+	bool activePlayer = false;
+	for (auto player : actedPlayers)
+	{
+		if (gameHandler->getPlayerState(player)->status == EPlayerStatus::INGAME)
+			activePlayer = true;
+	}
+
+	if(!activePlayer)
+		gameHandler->gameLobby()->setState(EServerState::GAMEPLAY_ENDED);
+
+	std::swap(actedPlayers, awaitingPlayers);
+}
+
+void TurnOrderProcessor::doStartPlayerTurn(PlayerColor which)
+{
+	//if player runs out of time, he shouldn't get the turn (especially AI)
+	//pre-trigger may change anything, should check before each player
+	//TODO: is it enough to check only one player?
+	gameHandler->checkVictoryLossConditionsForAll();
+
+	assert(gameHandler->getPlayerState(which));
+	assert(gameHandler->getPlayerState(which)->status == EPlayerStatus::INGAME);
+
+	gameHandler->onPlayerTurnStarted(which);
+
+	YourTurn yt;
+	yt.player = which;
+	//Change local daysWithoutCastle counter for local interface message //TODO: needed?
+	yt.daysWithoutCastle = gameHandler->getPlayerState(which)->daysWithoutCastle;
+	gameHandler->sendAndApply(&yt);
+}
+
+void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which)
+{
+	assert(playerMakingTurn(which));
+
+	actingPlayers.erase(which);
+	actedPlayers.insert(which);
+
+	if (!awaitingPlayers.empty())
+		tryStartTurnsForPlayers();
+
+	if (actingPlayers.empty())
+		doStartNewDay();
+}
+
+void TurnOrderProcessor::addPlayer(PlayerColor which)
+{
+	awaitingPlayers.insert(which);
+}
+
+bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which)
+{
+	if (!playerMakingTurn(which))
+	{
+		gameHandler->complain("Can not end turn for player that is not acting!");
+		return false;
+	}
+
+	if(gameHandler->getPlayerStatus(which) != EPlayerStatus::INGAME)
+	{
+		gameHandler->complain("Can not end turn for player that is not in game!");
+		return false;
+	}
+
+	if(gameHandler->queries->topQuery(which) != nullptr)
+	{
+		gameHandler->complain("Cannot end turn before resolving queries!");
+		return false;
+	}
+
+	doEndPlayerTurn(which);
+	return true;
+}
+
+void TurnOrderProcessor::onGameStarted()
+{
+	tryStartTurnsForPlayers();
+
+	// this may be game load - send notification to players that they can act
+	auto actingPlayersCopy = actingPlayers;
+	for (auto player : actingPlayersCopy)
+		doStartPlayerTurn(player);
+}
+
+void TurnOrderProcessor::tryStartTurnsForPlayers()
+{
+	auto awaitingPlayersCopy = awaitingPlayers;
+	for (auto player : awaitingPlayersCopy)
+	{
+		if (canStartTurn(player))
+			doStartPlayerTurn(player);
+	}
+}
+
+bool TurnOrderProcessor::playerAwaitsTurn(PlayerColor which) const
+{
+	return vstd::contains(awaitingPlayers, which);
+}
+
+bool TurnOrderProcessor::playerMakingTurn(PlayerColor which) const
+{
+	return vstd::contains(actingPlayers, which);
+}
+
+bool TurnOrderProcessor::playerAwaitsNewDay(PlayerColor which) const
+{
+	return vstd::contains(actedPlayers, which);
+}

+ 67 - 0
server/processors/TurnOrderProcessor.h

@@ -0,0 +1,67 @@
+/*
+ * TurnOrderProcessor.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/GameConstants.h"
+
+class CGameHandler;
+
+class TurnOrderProcessor : boost::noncopyable
+{
+	CGameHandler * gameHandler;
+
+	std::set<PlayerColor> awaitingPlayers;
+	std::set<PlayerColor> actingPlayers;
+	std::set<PlayerColor> actedPlayers;
+
+	/// Returns true if waiting player can act alongside with currently acting player
+	bool canActSimultaneously(PlayerColor active, PlayerColor waiting) const;
+
+	/// Returns true if left player must act before right player
+	bool mustActBefore(PlayerColor left, PlayerColor right) const;
+
+	/// Returns true if player is ready to start turn
+	bool canStartTurn(PlayerColor which) const;
+
+	/// Starts turn for all players that can start turn
+	void tryStartTurnsForPlayers();
+
+	void doStartNewDay();
+	void doStartPlayerTurn(PlayerColor which);
+	void doEndPlayerTurn(PlayerColor which);
+
+public:
+	TurnOrderProcessor(CGameHandler * owner);
+
+	/// Add new player to handle (e.g. on game start)
+	void addPlayer(PlayerColor which);
+
+	/// NetPack call-in
+	bool onPlayerEndsTurn(PlayerColor which);
+
+	/// Start game (or resume from save) and send YourTurn pack to player(s)
+	void onGameStarted();
+
+	/// Returns true if player turn has not started today
+	bool playerAwaitsTurn(PlayerColor which) const;
+
+	/// Returns true if player is currently making his turn
+	bool playerMakingTurn(PlayerColor which) const;
+
+	/// Returns true if player has finished his turn and is waiting for new day
+	bool playerAwaitsNewDay(PlayerColor which) const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & awaitingPlayers;
+		h & actingPlayers;
+		h & actedPlayers;
+	}
+};