/* * 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 "../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; } void TurnOrderProcessor::updateContactStatus() { blockedContacts.clear(); assert(actedPlayers.empty()); assert(actingPlayers.empty()); for (auto left : awaitingPlayers) { for(auto right : awaitingPlayers) { if (left == right) continue; if (computeCanActSimultaneously(left, right)) blockedContacts.push_back({left, right}); } } } bool TurnOrderProcessor::playersInContact(PlayerColor left, PlayerColor right) const { // TODO: refactor, cleanup and optimize boost::multi_array leftReachability; boost::multi_array 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(out, gameHandler->gameState(), hero); 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(out, gameHandler->gameState(), hero); 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::GAMEPLAY_ENDED); return; } std::swap(actedPlayers, awaitingPlayers); gameHandler->onNewTurn(); updateContactStatus(); tryStartTurnsForPlayers(); } void TurnOrderProcessor::doStartPlayerTurn(PlayerColor which) { assert(gameHandler->getPlayerState(which)); assert(gameHandler->getPlayerState(which)->status == EPlayerStatus::INGAME); //Note: on game load, "actingPlayer" might already contain list of players actingPlayers.insert(which); awaitingPlayers.erase(which); gameHandler->onPlayerTurnStarted(which); auto turnQuery = std::make_shared(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()) updateContactStatus(); // 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); }