| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 | 
							- /*
 
-  * 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 "PlayerMessageProcessor.h"
 
- #include "../queries/QueriesProcessor.h"
 
- #include "../queries/MapQueries.h"
 
- #include "../CGameHandler.h"
 
- #include "../CVCMIServer.h"
 
- #include "../../lib/CPlayerState.h"
 
- #include "../../lib/pathfinder/CPathfinder.h"
 
- #include "../../lib/pathfinder/PathfinderOptions.h"
 
- TurnOrderProcessor::TurnOrderProcessor(CGameHandler * owner):
 
- 	gameHandler(owner)
 
- {
 
- }
 
- int TurnOrderProcessor::simturnsTurnsMaxLimit() const
 
- {
 
- 	return gameHandler->getStartInfo()->simturnsInfo.optionalTurns;
 
- }
 
- int TurnOrderProcessor::simturnsTurnsMinLimit() const
 
- {
 
- 	return gameHandler->getStartInfo()->simturnsInfo.requiredTurns;
 
- }
 
- std::vector<TurnOrderProcessor::PlayerPair> TurnOrderProcessor::computeContactStatus() const
 
- {
 
- 	std::vector<PlayerPair> result;
 
- 	assert(actedPlayers.empty());
 
- 	assert(actingPlayers.empty());
 
- 	for (auto left : awaitingPlayers)
 
- 	{
 
- 		for(auto right : awaitingPlayers)
 
- 		{
 
- 			if (left == right)
 
- 				continue;
 
- 			if (computeCanActSimultaneously(left, right))
 
- 				result.push_back({left, right});
 
- 		}
 
- 	}
 
- 	return result;
 
- }
 
- void TurnOrderProcessor::updateAndNotifyContactStatus()
 
- {
 
- 	auto newBlockedContacts = computeContactStatus();
 
- 	if (newBlockedContacts.empty())
 
- 	{
 
- 		// Simturns between all players have ended - send single global notification
 
- 		if (!blockedContacts.empty())
 
- 			gameHandler->playerMessages->broadcastSystemMessage("Simultaneous turns have ended");
 
- 	}
 
- 	else
 
- 	{
 
- 		// Simturns between some players have ended - notify each pair
 
- 		for (auto const & contact : blockedContacts)
 
- 		{
 
- 			if (vstd::contains(newBlockedContacts, contact))
 
- 				continue;
 
- 			MetaString message;
 
- 			message.appendRawString("Simultaneous turns between players %s and %s have ended"); // FIXME: we should send MetaString itself and localize it on client side
 
- 			message.replaceName(contact.a);
 
- 			message.replaceName(contact.b);
 
- 			gameHandler->playerMessages->broadcastSystemMessage(message.toString());
 
- 		}
 
- 	}
 
- 	blockedContacts = newBlockedContacts;
 
- }
 
- bool TurnOrderProcessor::playersInContact(PlayerColor left, PlayerColor right) const
 
- {
 
- 	// TODO: refactor, cleanup and optimize
 
- 	boost::multi_array<bool, 3> leftReachability;
 
- 	boost::multi_array<bool, 3> rightReachability;
 
- 	int3 mapSize = gameHandler->getMapSize();
 
- 	leftReachability.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]);
 
- 	rightReachability.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]);
 
- 	const auto * leftInfo = gameHandler->getPlayerState(left, false);
 
- 	const auto * rightInfo = gameHandler->getPlayerState(right, false);
 
- 	for(const auto & hero : leftInfo->heroes)
 
- 	{
 
- 		CPathsInfo out(mapSize, hero);
 
- 		auto config = std::make_shared<SingleHeroPathfinderConfig>(out, gameHandler->gameState(), hero);
 
- 		config->options.ignoreGuards = true;
 
- 		config->options.turnLimit = 1;
 
- 		CPathfinder pathfinder(gameHandler->gameState(), config);
 
- 		pathfinder.calculatePaths();
 
- 		for (int z = 0; z < mapSize.z; ++z)
 
- 			for (int y = 0; y < mapSize.y; ++y)
 
- 				for (int x = 0; x < mapSize.x; ++x)
 
- 					if (out.getNode({x,y,z})->reachable())
 
- 						leftReachability[z][x][y] = true;
 
- 	}
 
- 	for(const auto & hero : rightInfo->heroes)
 
- 	{
 
- 		CPathsInfo out(mapSize, hero);
 
- 		auto config = std::make_shared<SingleHeroPathfinderConfig>(out, gameHandler->gameState(), hero);
 
- 		config->options.ignoreGuards = true;
 
- 		config->options.turnLimit = 1;
 
- 		CPathfinder pathfinder(gameHandler->gameState(), config);
 
- 		pathfinder.calculatePaths();
 
- 		for (int z = 0; z < mapSize.z; ++z)
 
- 			for (int y = 0; y < mapSize.y; ++y)
 
- 				for (int x = 0; x < mapSize.x; ++x)
 
- 					if (out.getNode({x,y,z})->reachable())
 
- 						rightReachability[z][x][y] = true;
 
- 	}
 
- 	for (int z = 0; z < mapSize.z; ++z)
 
- 		for (int y = 0; y < mapSize.y; ++y)
 
- 			for (int x = 0; x < mapSize.x; ++x)
 
- 				if (leftReachability[z][x][y] && rightReachability[z][x][y])
 
- 					return true;
 
- 	return false;
 
- }
 
- bool TurnOrderProcessor::isContactAllowed(PlayerColor active, PlayerColor waiting) const
 
- {
 
- 	assert(active != waiting);
 
- 	return !vstd::contains(blockedContacts, PlayerPair{active, waiting});
 
- }
 
- bool TurnOrderProcessor::computeCanActSimultaneously(PlayerColor active, PlayerColor waiting) const
 
- {
 
- 	const auto * activeInfo = gameHandler->getPlayerState(active, false);
 
- 	const auto * waitingInfo = gameHandler->getPlayerState(waiting, false);
 
- 	assert(active != waiting);
 
- 	assert(activeInfo);
 
- 	assert(waitingInfo);
 
- 	if (gameHandler->hasBothPlayersAtSameConnection(active, waiting))
 
- 	{
 
- 		if (!gameHandler->getStartInfo()->simturnsInfo.allowHumanWithAI)
 
- 			return false;
 
- 		// only one AI and one human can play simultaneoulsy from single connection
 
- 		if (activeInfo->human == waitingInfo->human)
 
- 			return false;
 
- 	}
 
- 	if (gameHandler->getDate(Date::DAY) < simturnsTurnsMinLimit())
 
- 		return true;
 
- 	if (gameHandler->getDate(Date::DAY) > simturnsTurnsMaxLimit())
 
- 		return false;
 
- 	if (playersInContact(active, waiting))
 
- 		return false;
 
- 	return true;
 
- }
 
- 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 false;
 
- }
 
- bool TurnOrderProcessor::canStartTurn(PlayerColor which) const
 
- {
 
- 	for (auto player : awaitingPlayers)
 
- 	{
 
- 		if (player != which && mustActBefore(player, which))
 
- 			return false;
 
- 	}
 
- 	for (auto player : actingPlayers)
 
- 	{
 
- 		if (player != which && isContactAllowed(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::SHUTDOWN);
 
- 		return;
 
- 	}
 
- 	std::swap(actedPlayers, awaitingPlayers);
 
- 	gameHandler->onNewTurn();
 
- 	updateAndNotifyContactStatus();
 
- 	tryStartTurnsForPlayers();
 
- }
 
- void TurnOrderProcessor::doStartPlayerTurn(PlayerColor which)
 
- {
 
- 	assert(gameHandler->getPlayerState(which));
 
- 	assert(gameHandler->getPlayerState(which)->status == EPlayerStatus::INGAME);
 
- 	// Only if player is actually starting his turn (and not loading from save)
 
- 	if (!actingPlayers.count(which))
 
- 		gameHandler->onPlayerTurnStarted(which);
 
- 	actingPlayers.insert(which);
 
- 	awaitingPlayers.erase(which);
 
- 	auto turnQuery = std::make_shared<TimerPauseQuery>(gameHandler, which);
 
- 	gameHandler->queries->addQuery(turnQuery);
 
- 	PlayerStartsTurn pst;
 
- 	pst.player = which;
 
- 	pst.queryID = turnQuery->queryID;
 
- 	gameHandler->sendAndApply(&pst);
 
- 	assert(!actingPlayers.empty());
 
- }
 
- void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which)
 
- {
 
- 	assert(isPlayerMakingTurn(which));
 
- 	assert(gameHandler->getPlayerStatus(which) == EPlayerStatus::INGAME);
 
- 	actingPlayers.erase(which);
 
- 	actedPlayers.insert(which);
 
- 	PlayerEndsTurn pet;
 
- 	pet.player = which;
 
- 	gameHandler->sendAndApply(&pet);
 
- 	if (!awaitingPlayers.empty())
 
- 		tryStartTurnsForPlayers();
 
- 	if (actingPlayers.empty())
 
- 		doStartNewDay();
 
- 	assert(!actingPlayers.empty());
 
- }
 
- void TurnOrderProcessor::addPlayer(PlayerColor which)
 
- {
 
- 	awaitingPlayers.insert(which);
 
- }
 
- void TurnOrderProcessor::onPlayerEndsGame(PlayerColor which)
 
- {
 
- 	awaitingPlayers.erase(which);
 
- 	actingPlayers.erase(which);
 
- 	actedPlayers.erase(which);
 
- 	if (!awaitingPlayers.empty())
 
- 		tryStartTurnsForPlayers();
 
- 	if (actingPlayers.empty())
 
- 		doStartNewDay();
 
- }
 
- bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which)
 
- {
 
- 	if (!isPlayerMakingTurn(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;
 
- 	}
 
- 	gameHandler->onPlayerTurnEnded(which);
 
- 	// it is possible that player have lost - e.g. spent 7 days without town
 
- 	// in this case - don't call doEndPlayerTurn - turn transfer was already handled by onPlayerEndsGame
 
- 	if(gameHandler->getPlayerStatus(which) == EPlayerStatus::INGAME)
 
- 		doEndPlayerTurn(which);
 
- 	return true;
 
- }
 
- void TurnOrderProcessor::onGameStarted()
 
- {
 
- 	if (actingPlayers.empty())
 
- 		blockedContacts = computeContactStatus();
 
- 	// this may be game load - send notification to players that they can act
 
- 	auto actingPlayersCopy = actingPlayers;
 
- 	for (auto player : actingPlayersCopy)
 
- 		doStartPlayerTurn(player);
 
- 	tryStartTurnsForPlayers();
 
- }
 
- void TurnOrderProcessor::tryStartTurnsForPlayers()
 
- {
 
- 	auto awaitingPlayersCopy = awaitingPlayers;
 
- 	for (auto player : awaitingPlayersCopy)
 
- 	{
 
- 		if (canStartTurn(player))
 
- 			doStartPlayerTurn(player);
 
- 	}
 
- }
 
- bool TurnOrderProcessor::isPlayerAwaitsTurn(PlayerColor which) const
 
- {
 
- 	return vstd::contains(awaitingPlayers, which);
 
- }
 
- bool TurnOrderProcessor::isPlayerMakingTurn(PlayerColor which) const
 
- {
 
- 	return vstd::contains(actingPlayers, which);
 
- }
 
- bool TurnOrderProcessor::isPlayerAwaitsNewDay(PlayerColor which) const
 
- {
 
- 	return vstd::contains(actedPlayers, which);
 
- }
 
 
  |