1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Moved management of turn order into a new class

This commit is contained in:
Ivan Savenko 2023-08-22 18:45:13 +03:00
parent 979cf129bc
commit c4bc6840ea
17 changed files with 341 additions and 271 deletions

View File

@ -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;

View File

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

View File

@ -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,114 +956,31 @@ void CGameHandler::run(bool resume)
services()->scripts()->run(serverScripts);
#endif
if(resume)
if (!resume)
{
onNewTurn();
events::TurnStarted::defaultExecute(serverEventBus.get());
for(auto & player : gs->players)
turnTimerHandler.onGameplayStart(player.second);
}
else
events::GameResumed::defaultExecute(serverEventBus.get());
auto playerTurnOrder = generatePlayerTurnOrder();
if(!resume)
for(auto & playerColor : playerTurnOrder)
turnTimerHandler.onGameplayStart(gs->players[playerColor]);
while(lobby->state == EServerState::GAMEPLAY)
turnOrder->onGameStarted();
//wait till game is done
while(lobby->getState() == EServerState::GAMEPLAY)
{
if(!resume)
{
newTurn();
events::TurnStarted::defaultExecute(serverEventBus.get());
}
const int waitTime = 100; //ms
std::list<PlayerColor>::iterator it;
if (resume)
{
it = std::find(playerTurnOrder.begin(), playerTurnOrder.end(), gs->currentPlayer);
}
else
{
it = playerTurnOrder.begin();
}
turnTimerHandler.onPlayerMakingTurn(gs->players[gs->getCurrentPlayer()], waitTime);
if(gs->curB)
turnTimerHandler.onBattleLoop(waitTime);
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;
boost::this_thread::sleep_for(boost::chrono::milliseconds(waitTime));
}
}
std::list<PlayerColor> CGameHandler::generatePlayerTurnOrder() const
{
// Generate player turn order
std::list<PlayerColor> playerTurnOrder;
for (const auto & player : gs->players) // add human players first
{
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);
}
return playerTurnOrder;
}
void CGameHandler::giveSpells(const CGTownInstance *t, const CGHeroInstance *h)
{
if (!h->hasSpellbook())
@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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

View File

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

View File

@ -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!");
if (!gh.hasPlayerAt(pack.player, pack.c))
gh.throwAndComplain(&pack, "No such 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!");
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);
}

View File

@ -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)
{
}

View File

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

View File

@ -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

View File

@ -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;

View File

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

View File

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

View File

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

View File

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