mirror of
https://github.com/vcmi/vcmi.git
synced 2025-02-09 13:14:02 +02:00
before him are waiting to act. E.g. green can start turn even if blue and red are in contact and blue is yet to start his turn
337 lines
8.3 KiB
C++
337 lines
8.3 KiB
C++
/*
|
|
* 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<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);
|
|
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);
|
|
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);
|
|
|
|
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<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();
|
|
|
|
assert(!actingPlayers.empty());
|
|
}
|
|
|
|
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);
|
|
}
|