diff --git a/include/vcmi/events/PlayerGotTurn.h b/include/vcmi/events/PlayerGotTurn.h index a75380cca..162300701 100644 --- a/include/vcmi/events/PlayerGotTurn.h +++ b/include/vcmi/events/PlayerGotTurn.h @@ -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; diff --git a/lib/events/PlayerGotTurn.cpp b/lib/events/PlayerGotTurn.cpp index 123853d31..e4a259e99 100644 --- a/lib/events/PlayerGotTurn.cpp +++ b/lib/events/PlayerGotTurn.cpp @@ -24,11 +24,11 @@ SubscriptionRegistry * 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(); } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index ee726c596..3253d1cf4 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -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(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 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 l(mx); - players[player]; -} - -bool PlayerStatuses::checkFlag(PlayerColor player, bool PlayerStatus::*flag) -{ - boost::unique_lock 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 l(mx); - if (players.find(player) != players.end()) - { - players[player].*flag = val; - } - else - { - throw std::runtime_error("No such player!"); - } - cv.notify_all(); -} - template void callWith(std::vector args, std::function fun, ui32 which) { @@ -480,7 +436,7 @@ void CGameHandler::changeSecSkill(const CGHeroInstance * hero, SecondarySkill wh void CGameHandler::handleClientDisconnection(std::shared_ptr 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(this)) , battles(std::make_unique(this)) + , turnOrder(std::make_unique(this)) , queries(std::make_unique()) , playerMessages(std::make_unique(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::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 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 CGameHandler::generatePlayerTurnOrder() const -{ - // Generate player turn order - std::list 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); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 8d5f354ab..555257537 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -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 void serialize(Handler &h, const int version) - { - h & makingTurn; - } -}; -class PlayerStatuses -{ -public: - std::map 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 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 heroPool; std::unique_ptr battles; std::unique_ptr queries; + std::unique_ptr 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 playerMessages; std::map>> 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 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 & slot); void spawnWanderingMonsters(CreatureID creatureID); @@ -298,8 +271,6 @@ public: scripting::Pool * getContextPool() const override; #endif - std::list generatePlayerTurnOrder() const; - friend class CVCMIServer; private: std::unique_ptr serverEventBus; @@ -308,7 +279,6 @@ private: #endif void reinitScripting(); - void deserializationFix(); void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const; diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 7bd113de6..0263fef96 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -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 diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index c00a0b801..d72f78d0c 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -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) diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 5ecd942d5..beb3be62b 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -56,10 +56,10 @@ class CVCMIServer : public LobbyInfo boost::recursive_mutex mx; std::shared_ptr> applier; std::unique_ptr announceLobbyThread, remoteConnectionsThread; + std::atomic state; public: std::shared_ptr gh; - std::atomic 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 diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index 54c31236a..2f2695c11 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -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 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(*srv.gh->getStartInfo(true)); pack.initializedGameState = srv.gh->gameState(); - srv.state = EServerState::GAMEPLAY_STARTING; + srv.setState(EServerState::GAMEPLAY_STARTING); result = true; } diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index b0b61674c..b49392edf 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -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); } diff --git a/server/ServerNetPackVisitors.h b/server/ServerNetPackVisitors.h index 821046418..e338cf00d 100644 --- a/server/ServerNetPackVisitors.h +++ b/server/ServerNetPackVisitors.h @@ -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) { } diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index f61b4e4d8..d2e568273 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -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); } } diff --git a/server/battles/BattleFlowProcessor.cpp b/server/battles/BattleFlowProcessor.cpp index 0f0837a47..8d24f15a0 100644 --- a/server/battles/BattleFlowProcessor.cpp +++ b/server/battles/BattleFlowProcessor.cpp @@ -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 diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index 180234388..47c592876 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -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; diff --git a/server/processors/HeroPoolProcessor.h b/server/processors/HeroPoolProcessor.h index dc69a7cf3..3cf48ca5f 100644 --- a/server/processors/HeroPoolProcessor.h +++ b/server/processors/HeroPoolProcessor.h @@ -43,7 +43,6 @@ class HeroPoolProcessor : boost::noncopyable TavernHeroSlot selectSlotForRole(const PlayerColor & player, TavernSlotRole roleID); - bool playerEndedTurn(const PlayerColor & player); public: CGameHandler * gameHandler; diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index 75e4217fc..26c94c835 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -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; } diff --git a/server/processors/TurnOrderProcessor.cpp b/server/processors/TurnOrderProcessor.cpp new file mode 100644 index 000000000..bd8b0a95e --- /dev/null +++ b/server/processors/TurnOrderProcessor.cpp @@ -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); +} diff --git a/server/processors/TurnOrderProcessor.h b/server/processors/TurnOrderProcessor.h new file mode 100644 index 000000000..69a95ec71 --- /dev/null +++ b/server/processors/TurnOrderProcessor.h @@ -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 awaitingPlayers; + std::set actingPlayers; + std::set 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 void serialize(Handler &h, const int version) + { + h & awaitingPlayers; + h & actingPlayers; + h & actedPlayers; + } +};