1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-09 13:14:02 +02:00
vcmi/server/processors/TurnOrderProcessor.cpp
Ivan Savenko 18c0217679 Relaxed ordering requirements - player can start turn even if players
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
2023-11-09 00:08:13 +02:00

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