From c4bc6840eaef46407b4690c769f13074fb14ae7f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 22 Aug 2023 18:45:13 +0300 Subject: [PATCH 01/14] Moved management of turn order into a new class --- include/vcmi/events/PlayerGotTurn.h | 2 +- lib/events/PlayerGotTurn.cpp | 4 +- server/CGameHandler.cpp | 204 ++++--------------- server/CGameHandler.h | 50 +---- server/CMakeLists.txt | 2 + server/CVCMIServer.cpp | 14 +- server/CVCMIServer.h | 5 +- server/NetPacksLobbyServer.cpp | 16 +- server/NetPacksServer.cpp | 23 +-- server/ServerNetPackVisitors.h | 5 +- server/TurnTimerHandler.cpp | 5 +- server/battles/BattleFlowProcessor.cpp | 1 - server/processors/HeroPoolProcessor.cpp | 28 +-- server/processors/HeroPoolProcessor.h | 1 - server/processors/PlayerMessageProcessor.cpp | 2 +- server/processors/TurnOrderProcessor.cpp | 183 +++++++++++++++++ server/processors/TurnOrderProcessor.h | 67 ++++++ 17 files changed, 341 insertions(+), 271 deletions(-) create mode 100644 server/processors/TurnOrderProcessor.cpp create mode 100644 server/processors/TurnOrderProcessor.h 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; + } +}; From d83aa828f63597b0afd07c925056edc927108822 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 23 Aug 2023 18:46:30 +0300 Subject: [PATCH 02/14] Fix turn ending --- AI/Nullkiller/Analyzers/ArmyManager.cpp | 3 +- include/vcmi/events/PlayerGotTurn.h | 2 +- lib/events/PlayerGotTurn.cpp | 3 +- server/CGameHandler.cpp | 5 ++-- server/NetPacksServer.cpp | 2 +- server/TurnTimerHandler.cpp | 2 +- server/processors/TurnOrderProcessor.cpp | 37 ++++++++++++++++-------- server/processors/TurnOrderProcessor.h | 11 +++++-- 8 files changed, 43 insertions(+), 22 deletions(-) diff --git a/AI/Nullkiller/Analyzers/ArmyManager.cpp b/AI/Nullkiller/Analyzers/ArmyManager.cpp index 19f48ed65..11bdeecd5 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.cpp +++ b/AI/Nullkiller/Analyzers/ArmyManager.cpp @@ -225,7 +225,8 @@ std::vector ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, if(weakest->count == 1) { - assert(resultingArmy.size() > 1); + if (resultingArmy.size() == 1) + logAi->warn("Unexpected resulting army size!"); resultingArmy.erase(weakest); } diff --git a/include/vcmi/events/PlayerGotTurn.h b/include/vcmi/events/PlayerGotTurn.h index 162300701..278e03c88 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, PlayerColor & player); + static void defaultExecute(const EventBus * bus, const 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 e4a259e99..ccbffecc5 100644 --- a/lib/events/PlayerGotTurn.cpp +++ b/lib/events/PlayerGotTurn.cpp @@ -24,12 +24,11 @@ SubscriptionRegistry * PlayerGotTurn::getRegistry() return Instance.get(); } -void PlayerGotTurn::defaultExecute(const EventBus * bus, PlayerColor & player) +void PlayerGotTurn::defaultExecute(const EventBus * bus, const PlayerColor & player) { CPlayerGotTurn event; event.setPlayer(player); bus->executeEvent(event); - player = event.getPlayer(); } CPlayerGotTurn::CPlayerGotTurn() = default; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 3253d1cf4..4f2a51f48 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -609,7 +609,8 @@ void CGameHandler::onPlayerTurnStarted(PlayerColor which) void CGameHandler::onPlayerTurnEnded(PlayerColor which) { - + // 7 days without castle + checkVictoryLossConditionsForPlayer(which); } void CGameHandler::onNewTurn() @@ -3550,7 +3551,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) // 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) - turnOrder->onPlayerEndsTurn(gs->currentPlayer); + turnOrder->onPlayerEndsTurn(gs->currentPlayer, PlayerTurnEndReason::GAME_END); } } diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index b49392edf..69f11d885 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -40,7 +40,7 @@ void ApplyGhNetPackVisitor::visitEndTurn(EndTurn & pack) if (!gh.hasPlayerAt(pack.player, pack.c)) gh.throwAndComplain(&pack, "No such pack.player!"); - result = gh.turnOrder->onPlayerEndsTurn(pack.player); + result = gh.turnOrder->onPlayerEndsTurn(pack.player, PlayerTurnEndReason::CLIENT_REQUEST); } void ApplyGhNetPackVisitor::visitDismissHero(DismissHero & pack) diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index d2e568273..021ee59e1 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -85,7 +85,7 @@ void TurnTimerHandler::onPlayerMakingTurn(PlayerState & state, int waitTime) onPlayerMakingTurn(state, waitTime); } else if(!gameHandler.queries->topQuery(state.color)) - gameHandler.turnOrder->onPlayerEndsTurn(state.color); + gameHandler.turnOrder->onPlayerEndsTurn(state.color, PlayerTurnEndReason::TURN_TIMEOUT); } } diff --git a/server/processors/TurnOrderProcessor.cpp b/server/processors/TurnOrderProcessor.cpp index bd8b0a95e..5cbaea310 100644 --- a/server/processors/TurnOrderProcessor.cpp +++ b/server/processors/TurnOrderProcessor.cpp @@ -54,7 +54,7 @@ bool TurnOrderProcessor::canStartTurn(PlayerColor which) const { for (auto player : awaitingPlayers) { - if (mustActBefore(player, which)) + if (player != which && mustActBefore(player, which)) return false; } @@ -83,18 +83,19 @@ void TurnOrderProcessor::doStartNewDay() gameHandler->gameLobby()->setState(EServerState::GAMEPLAY_ENDED); std::swap(actedPlayers, awaitingPlayers); + + gameHandler->onNewTurn(); + tryStartTurnsForPlayers(); } 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); + //Note: on game load, "actingPlayer" might already contain list of players + actingPlayers.insert(which); + awaitingPlayers.erase(which); gameHandler->onPlayerTurnStarted(which); YourTurn yt; @@ -102,20 +103,28 @@ void TurnOrderProcessor::doStartPlayerTurn(PlayerColor which) //Change local daysWithoutCastle counter for local interface message //TODO: needed? yt.daysWithoutCastle = gameHandler->getPlayerState(which)->daysWithoutCastle; gameHandler->sendAndApply(&yt); + + assert(actingPlayers.size() == 1); // No simturns yet :( + assert(gameHandler->getCurrentPlayer() == *actingPlayers.begin()); } -void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which) +void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which, PlayerTurnEndReason reason) { assert(playerMakingTurn(which)); actingPlayers.erase(which); - actedPlayers.insert(which); + if (reason != PlayerTurnEndReason::GAME_END) + actedPlayers.insert(which); if (!awaitingPlayers.empty()) tryStartTurnsForPlayers(); if (actingPlayers.empty()) doStartNewDay(); + + assert(!actingPlayers.empty()); + assert(actingPlayers.size() == 1); // No simturns yet :( + assert(gameHandler->getCurrentPlayer() == *actingPlayers.begin()); } void TurnOrderProcessor::addPlayer(PlayerColor which) @@ -123,7 +132,7 @@ void TurnOrderProcessor::addPlayer(PlayerColor which) awaitingPlayers.insert(which); } -bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which) +bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which, PlayerTurnEndReason reason) { if (!playerMakingTurn(which)) { @@ -143,18 +152,22 @@ bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which) return false; } - doEndPlayerTurn(which); + if (reason != PlayerTurnEndReason::GAME_END) + gameHandler->onPlayerTurnEnded(which); + + doEndPlayerTurn(which, reason); + 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); + + tryStartTurnsForPlayers(); } void TurnOrderProcessor::tryStartTurnsForPlayers() diff --git a/server/processors/TurnOrderProcessor.h b/server/processors/TurnOrderProcessor.h index 69a95ec71..fd524d575 100644 --- a/server/processors/TurnOrderProcessor.h +++ b/server/processors/TurnOrderProcessor.h @@ -13,6 +13,13 @@ class CGameHandler; +enum class PlayerTurnEndReason +{ + CLIENT_REQUEST, // client requested end of turn (e.g. press End Turn button) + TURN_TIMEOUT, // Player's turn timer has run out + GAME_END // Player have won or lost the game +}; + class TurnOrderProcessor : boost::noncopyable { CGameHandler * gameHandler; @@ -35,7 +42,7 @@ class TurnOrderProcessor : boost::noncopyable void doStartNewDay(); void doStartPlayerTurn(PlayerColor which); - void doEndPlayerTurn(PlayerColor which); + void doEndPlayerTurn(PlayerColor which, PlayerTurnEndReason reason); public: TurnOrderProcessor(CGameHandler * owner); @@ -44,7 +51,7 @@ public: void addPlayer(PlayerColor which); /// NetPack call-in - bool onPlayerEndsTurn(PlayerColor which); + bool onPlayerEndsTurn(PlayerColor which, PlayerTurnEndReason reason); /// Start game (or resume from save) and send YourTurn pack to player(s) void onGameStarted(); From e40dc76304849d9e20990bf954eac8b48fbc04ad Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 24 Aug 2023 13:35:06 +0300 Subject: [PATCH 03/14] Fix giving commands in battles in hotseat --- client/battle/BattleInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index 5bc4133ca..deb640985 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -265,7 +265,7 @@ void BattleInterface::sendCommand(BattleAction command, const CStack * actor) { logGlobal->trace("Setting command for %s", (actor ? actor->nodeName() : "hero")); stacksController->setActiveStack(nullptr); - LOCPLINT->cb->battleMakeUnitAction(command); + curInt->cb->battleMakeUnitAction(command); } else { From 97ef69c9ab8bc877a89883758ffacc82683ed9b6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 24 Aug 2023 13:35:37 +0300 Subject: [PATCH 04/14] Remove incorrect message on another player defeat --- client/CPlayerInterface.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index c69bd1f75..a5a9a9c31 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1625,15 +1625,6 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul if (GH.curInt == this) GH.curInt = nullptr; } - else - { - if (victoryLossCheckResult.loss() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME) //enemy has lost - { - MetaString message = victoryLossCheckResult.messageToSelf; - message.appendLocalString(EMetaText::COLOR, player.getNum()); - showInfoDialog(message.toString(), std::vector>(1, std::make_shared(CComponent::flag, player.getNum(), 0))); - } - } } void CPlayerInterface::playerBonusChanged( const Bonus &bonus, bool gain ) From f451c1593282d5effd744c61f6c4187331111e0e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 24 Aug 2023 13:36:01 +0300 Subject: [PATCH 05/14] Fix handling of retreating of AI's --- server/battles/BattleProcessor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/battles/BattleProcessor.cpp b/server/battles/BattleProcessor.cpp index 50f2a31b2..b7e7a8e1f 100644 --- a/server/battles/BattleProcessor.cpp +++ b/server/battles/BattleProcessor.cpp @@ -219,7 +219,8 @@ void BattleProcessor::updateGateState() bool BattleProcessor::makePlayerBattleAction(PlayerColor player, const BattleAction &ba) { bool result = actionsProcessor->makePlayerBattleAction(player, ba); - flowProcessor->onActionMade(ba); + if (!resultProcessor->battleIsEnding()) + flowProcessor->onActionMade(ba); return result; } From a19cdb57bae870a68aa6fe1ad5f7a634311eb5c5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 24 Aug 2023 13:36:35 +0300 Subject: [PATCH 06/14] Fix handling of turn order in case of player defeat --- server/CGameHandler.cpp | 9 ++------ server/NetPacksServer.cpp | 2 +- server/TurnTimerHandler.cpp | 2 +- server/processors/TurnOrderProcessor.cpp | 29 ++++++++++++++++++------ server/processors/TurnOrderProcessor.h | 13 ++++------- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4f2a51f48..4ae780090 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3480,6 +3480,8 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) peg.victoryLossCheckResult = victoryLossCheckResult; sendAndApply(&peg); + turnOrder->onPlayerEndsGame(player); + if (victoryLossCheckResult.victory()) { //one player won -> all enemies lost @@ -3545,13 +3547,6 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) } checkVictoryLossConditions(playerColors); } - - 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) - turnOrder->onPlayerEndsTurn(gs->currentPlayer, PlayerTurnEndReason::GAME_END); } } diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 69f11d885..b49392edf 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -40,7 +40,7 @@ void ApplyGhNetPackVisitor::visitEndTurn(EndTurn & pack) if (!gh.hasPlayerAt(pack.player, pack.c)) gh.throwAndComplain(&pack, "No such pack.player!"); - result = gh.turnOrder->onPlayerEndsTurn(pack.player, PlayerTurnEndReason::CLIENT_REQUEST); + result = gh.turnOrder->onPlayerEndsTurn(pack.player); } void ApplyGhNetPackVisitor::visitDismissHero(DismissHero & pack) diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index 021ee59e1..d2e568273 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -85,7 +85,7 @@ void TurnTimerHandler::onPlayerMakingTurn(PlayerState & state, int waitTime) onPlayerMakingTurn(state, waitTime); } else if(!gameHandler.queries->topQuery(state.color)) - gameHandler.turnOrder->onPlayerEndsTurn(state.color, PlayerTurnEndReason::TURN_TIMEOUT); + gameHandler.turnOrder->onPlayerEndsTurn(state.color); } } diff --git a/server/processors/TurnOrderProcessor.cpp b/server/processors/TurnOrderProcessor.cpp index 5cbaea310..165d529c6 100644 --- a/server/processors/TurnOrderProcessor.cpp +++ b/server/processors/TurnOrderProcessor.cpp @@ -108,13 +108,12 @@ void TurnOrderProcessor::doStartPlayerTurn(PlayerColor which) assert(gameHandler->getCurrentPlayer() == *actingPlayers.begin()); } -void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which, PlayerTurnEndReason reason) +void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which) { assert(playerMakingTurn(which)); actingPlayers.erase(which); - if (reason != PlayerTurnEndReason::GAME_END) - actedPlayers.insert(which); + actedPlayers.insert(which); if (!awaitingPlayers.empty()) tryStartTurnsForPlayers(); @@ -132,7 +131,24 @@ void TurnOrderProcessor::addPlayer(PlayerColor which) awaitingPlayers.insert(which); } -bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which, PlayerTurnEndReason reason) +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()); + assert(actingPlayers.size() == 1); // No simturns yet :( + assert(gameHandler->getCurrentPlayer() == *actingPlayers.begin()); +} + +bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which) { if (!playerMakingTurn(which)) { @@ -152,10 +168,9 @@ bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which, PlayerTurnEndReason return false; } - if (reason != PlayerTurnEndReason::GAME_END) - gameHandler->onPlayerTurnEnded(which); + gameHandler->onPlayerTurnEnded(which); - doEndPlayerTurn(which, reason); + doEndPlayerTurn(which); return true; } diff --git a/server/processors/TurnOrderProcessor.h b/server/processors/TurnOrderProcessor.h index fd524d575..ea6e7676b 100644 --- a/server/processors/TurnOrderProcessor.h +++ b/server/processors/TurnOrderProcessor.h @@ -13,13 +13,6 @@ class CGameHandler; -enum class PlayerTurnEndReason -{ - CLIENT_REQUEST, // client requested end of turn (e.g. press End Turn button) - TURN_TIMEOUT, // Player's turn timer has run out - GAME_END // Player have won or lost the game -}; - class TurnOrderProcessor : boost::noncopyable { CGameHandler * gameHandler; @@ -42,7 +35,7 @@ class TurnOrderProcessor : boost::noncopyable void doStartNewDay(); void doStartPlayerTurn(PlayerColor which); - void doEndPlayerTurn(PlayerColor which, PlayerTurnEndReason reason); + void doEndPlayerTurn(PlayerColor which); public: TurnOrderProcessor(CGameHandler * owner); @@ -51,7 +44,9 @@ public: void addPlayer(PlayerColor which); /// NetPack call-in - bool onPlayerEndsTurn(PlayerColor which, PlayerTurnEndReason reason); + bool onPlayerEndsTurn(PlayerColor which); + + void onPlayerEndsGame(PlayerColor which); /// Start game (or resume from save) and send YourTurn pack to player(s) void onGameStarted(); From b6d8c7d4a55236307dedcf043c78fc26c2b5fddb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 24 Aug 2023 18:48:15 +0300 Subject: [PATCH 07/14] Fix retreating in player-with-player battles --- server/battles/BattleProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/battles/BattleProcessor.cpp b/server/battles/BattleProcessor.cpp index b7e7a8e1f..445fa5007 100644 --- a/server/battles/BattleProcessor.cpp +++ b/server/battles/BattleProcessor.cpp @@ -219,7 +219,7 @@ void BattleProcessor::updateGateState() bool BattleProcessor::makePlayerBattleAction(PlayerColor player, const BattleAction &ba) { bool result = actionsProcessor->makePlayerBattleAction(player, ba); - if (!resultProcessor->battleIsEnding()) + if (!resultProcessor->battleIsEnding() && gameHandler->gameState()->curB != nullptr) flowProcessor->onActionMade(ba); return result; } From c171a3d6be03c30262570c2e3127dfd47c1cd627 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 24 Aug 2023 18:48:47 +0300 Subject: [PATCH 08/14] Fix resource bar UI in hotseat --- client/adventureMap/AdventureMapWidget.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/adventureMap/AdventureMapWidget.cpp b/client/adventureMap/AdventureMapWidget.cpp index b8f50dcd4..65e1230be 100644 --- a/client/adventureMap/AdventureMapWidget.cpp +++ b/client/adventureMap/AdventureMapWidget.cpp @@ -372,11 +372,15 @@ void AdventureMapWidget::setPlayerChildren(CIntObject * widget, const PlayerColo auto container = dynamic_cast(entry); auto icon = dynamic_cast(entry); auto button = dynamic_cast(entry); + auto resDataBar = dynamic_cast(entry); auto texture = dynamic_cast(entry); if(button) button->setPlayerColor(player); + if(resDataBar) + resDataBar->colorize(player); + if(icon) icon->setPlayer(player); From ee8adbe85f01b8494d902ddd3eb9c1d2d89d8079 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 24 Aug 2023 18:53:58 +0300 Subject: [PATCH 09/14] Update tavern on end of 7th turn of player in question Allows removal of "retreat after 7th day" workaround and as result - more straightforward code --- lib/gameState/TavernHeroesPool.cpp | 9 ------- lib/gameState/TavernSlot.h | 6 +---- server/CGameHandler.cpp | 11 +++++--- server/processors/HeroPoolProcessor.cpp | 34 +++++-------------------- 4 files changed, 15 insertions(+), 45 deletions(-) diff --git a/lib/gameState/TavernHeroesPool.cpp b/lib/gameState/TavernHeroesPool.cpp index f5e5a6138..70f441f98 100644 --- a/lib/gameState/TavernHeroesPool.cpp +++ b/lib/gameState/TavernHeroesPool.cpp @@ -124,15 +124,6 @@ void TavernHeroesPool::onNewDay() hero.second->setMovementPoints(hero.second->movementPointsLimit(true)); hero.second->mana = hero.second->manaLimit(); } - - for (auto & slot : currentTavern) - { - if (slot.role == TavernSlotRole::RETREATED_TODAY) - slot.role = TavernSlotRole::RETREATED; - - if (slot.role == TavernSlotRole::SURRENDERED_TODAY) - slot.role = TavernSlotRole::SURRENDERED; - } } void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero) diff --git a/lib/gameState/TavernSlot.h b/lib/gameState/TavernSlot.h index 192fd047d..698168cff 100644 --- a/lib/gameState/TavernSlot.h +++ b/lib/gameState/TavernSlot.h @@ -24,12 +24,8 @@ enum class TavernSlotRole : int8_t SINGLE_UNIT, // hero was added after buying hero from this slot, and only has 1 creature in army FULL_ARMY, // hero was added to tavern on new week and still has full army - RETREATED, // hero was owned by player before, but have retreated from battle and only has 1 creature in army - RETREATED_TODAY, - - SURRENDERED, // hero was owned by player before, but have surrendered in battle and kept some troops - SURRENDERED_TODAY, + SURRENDERED // hero was owned by player before, but have surrendered in battle and kept some troops }; VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4ae780090..b8ca99c58 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -611,6 +611,11 @@ void CGameHandler::onPlayerTurnEnded(PlayerColor which) { // 7 days without castle checkVictoryLossConditionsForPlayer(which); + + bool newWeek = getDate(Date::DAY_OF_WEEK) == 7; // end of 7th day + + if (newWeek) //new heroes in tavern + heroPool->onNewWeek(which); } void CGameHandler::onNewTurn() @@ -702,13 +707,13 @@ void CGameHandler::onNewTurn() { if (elem.first == PlayerColor::NEUTRAL) continue; - else if (elem.first >= PlayerColor::PLAYER_LIMIT) - assert(0); //illegal player number! + + assert(elem.first.isValidPlayer());//illegal player number! std::pair playerGold(elem.first, elem.second.resources[EGameResID::GOLD]); hadGold.insert(playerGold); - if (newWeek) //new heroes in tavern + if (firstTurn) heroPool->onNewWeek(elem.first); n.res[elem.first] = elem.second.resources; diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index 47c592876..1881f8083 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -68,10 +68,7 @@ TavernHeroSlot HeroPoolProcessor::selectSlotForRole(const PlayerColor & player, void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHeroInstance * hero) { SetAvailableHero sah; - if (gameHandler->turnOrder->playerAwaitsNewDay(color)) - sah.roleID = TavernSlotRole::SURRENDERED_TODAY; - else - sah.roleID = TavernSlotRole::SURRENDERED; + sah.roleID = TavernSlotRole::SURRENDERED; sah.slotID = selectSlotForRole(color, sah.roleID); sah.player = color; @@ -82,10 +79,7 @@ void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHer void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero) { SetAvailableHero sah; - if (gameHandler->turnOrder->playerAwaitsNewDay(color)) - sah.roleID = TavernSlotRole::RETREATED_TODAY; - else - sah.roleID = TavernSlotRole::RETREATED; + sah.roleID = TavernSlotRole::RETREATED; sah.slotID = selectSlotForRole(color, sah.roleID); sah.player = color; @@ -139,26 +133,10 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe void HeroPoolProcessor::onNewWeek(const PlayerColor & color) { - const auto & heroesPool = gameHandler->gameState()->heroesPool; - const auto & heroes = heroesPool->getHeroesFor(color); - - const auto nativeSlotRole = heroes.size() < 1 ? TavernSlotRole::NONE : heroesPool->getSlotRole(heroes[0]->type->getId()); - const auto randomSlotRole = heroes.size() < 2 ? TavernSlotRole::NONE : heroesPool->getSlotRole(heroes[1]->type->getId()); - - bool resetNativeSlot = nativeSlotRole != TavernSlotRole::RETREATED_TODAY && nativeSlotRole != TavernSlotRole::SURRENDERED_TODAY; - bool resetRandomSlot = randomSlotRole != TavernSlotRole::RETREATED_TODAY && randomSlotRole != TavernSlotRole::SURRENDERED_TODAY; - - if (resetNativeSlot) - clearHeroFromSlot(color, TavernHeroSlot::NATIVE); - - if (resetRandomSlot) - clearHeroFromSlot(color, TavernHeroSlot::RANDOM); - - if (resetNativeSlot) - selectNewHeroForSlot(color, TavernHeroSlot::NATIVE, true, true); - - if (resetRandomSlot) - selectNewHeroForSlot(color, TavernHeroSlot::RANDOM, false, true); + clearHeroFromSlot(color, TavernHeroSlot::NATIVE); + clearHeroFromSlot(color, TavernHeroSlot::RANDOM); + selectNewHeroForSlot(color, TavernHeroSlot::NATIVE, true, true); + selectNewHeroForSlot(color, TavernHeroSlot::RANDOM, false, true); } bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTypeID & heroToRecruit, const PlayerColor & player) From b057230d854692f8703be38c8bc5470c823bfe6b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 24 Aug 2023 19:10:39 +0300 Subject: [PATCH 10/14] Fix quest log button block status --- client/adventureMap/AdventureMapShortcuts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index 19ee7b455..89415023e 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -397,7 +397,7 @@ void AdventureMapShortcuts::moveHeroDirectional(const Point & direction) bool AdventureMapShortcuts::optionCanViewQuests() { - return optionInMapView() && CGI->mh->getMap()->quests.empty(); + return optionInMapView() && !CGI->mh->getMap()->quests.empty(); } bool AdventureMapShortcuts::optionCanToggleLevel() From 66f555f1f7ebde7ab90f172731258342b1bc3653 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 24 Aug 2023 19:10:53 +0300 Subject: [PATCH 11/14] Fix error messages on bad morale --- server/battles/BattleActionProcessor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index 18da73a5b..878da7417 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -499,6 +499,7 @@ bool BattleActionProcessor::dispatchBattleAction(const BattleAction & ba) { switch(ba.actionType) { + case EActionType::BAD_MORALE: case EActionType::NO_ACTION: return doEmptyAction(ba); case EActionType::END_TACTIC_PHASE: From f9410145d6f18c731f1f21a9cb471fc4ac4a8732 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 24 Aug 2023 21:35:01 +0300 Subject: [PATCH 12/14] Fix handling of "7 days without town" loss condition --- client/ClientCommandManager.cpp | 1 - lib/NetPackVisitor.h | 1 + lib/NetPacks.h | 16 +++++++++- lib/NetPacksLib.cpp | 40 +++++++++++------------- lib/registerTypes/RegisterTypes.h | 1 + server/CGameHandler.cpp | 23 +++++++++++++- server/TurnTimerHandler.cpp | 2 +- server/processors/TurnOrderProcessor.cpp | 8 +++-- server/processors/TurnOrderProcessor.h | 17 +++++----- 9 files changed, 70 insertions(+), 39 deletions(-) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 9de0ba3d9..c9cce134e 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -471,7 +471,6 @@ void ClientCommandManager::giveTurn(const PlayerColor &colorIdentifier) { YourTurn yt; yt.player = colorIdentifier; - yt.daysWithoutCastle = CSH->client->getPlayerState(colorIdentifier)->daysWithoutCastle; ApplyClientNetPackVisitor visitor(*CSH->client, *CSH->client->gameState()); yt.visit(visitor); diff --git a/lib/NetPackVisitor.h b/lib/NetPackVisitor.h index f0c82b290..bf740e46b 100644 --- a/lib/NetPackVisitor.h +++ b/lib/NetPackVisitor.h @@ -27,6 +27,7 @@ public: virtual void visitPlayerBlocked(PlayerBlocked & pack) {} virtual void visitPlayerCheated(PlayerCheated & pack) {} virtual void visitYourTurn(YourTurn & pack) {} + virtual void visitYourTurn(DaysWithoutTown & pack) {} virtual void visitTurnTimeUpdate(TurnTimeUpdate & pack) {} virtual void visitEntitiesChanged(EntitiesChanged & pack) {} virtual void visitSetResources(SetResources & pack) {} diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 8fd625266..afcbe16c4 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -167,7 +167,21 @@ struct DLL_LINKAGE YourTurn : public CPackForClient void applyGs(CGameState * gs) const; PlayerColor player; - std::optional daysWithoutCastle; + + virtual void visitTyped(ICPackVisitor & visitor) override; + + template void serialize(Handler & h, const int version) + { + h & player; + } +}; + +struct DLL_LINKAGE DaysWithoutTown : public CPackForClient +{ + void applyGs(CGameState * gs) const; + + PlayerColor player; + std::optional daysWithoutCastle; virtual void visitTyped(ICPackVisitor & visitor) override; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 2f1cd86a5..e296c2273 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -108,6 +108,11 @@ void YourTurn::visitTyped(ICPackVisitor & visitor) visitor.visitYourTurn(*this); } +void DaysWithoutTown::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitYourTurn(*this); +} + void EntitiesChanged::visitTyped(ICPackVisitor & visitor) { visitor.visitEntitiesChanged(*this); @@ -2025,26 +2030,6 @@ void NewTurn::applyGs(CGameState *gs) if(gs->getDate(Date::DAY_OF_WEEK) == 1) gs->updateRumor(); - - //count days without town for all players, regardless of their turn order - for (auto &p : gs->players) - { - PlayerState & playerState = p.second; - if (playerState.status == EPlayerStatus::INGAME) - { - if (playerState.towns.empty()) - { - if (playerState.daysWithoutCastle) - ++(*playerState.daysWithoutCastle); - else - playerState.daysWithoutCastle = std::make_optional(0); - } - else - { - playerState.daysWithoutCastle = std::nullopt; - } - } - } } void SetObjectProperty::applyGs(CGameState * gs) const @@ -2063,8 +2048,16 @@ void SetObjectProperty::applyGs(CGameState * gs) const { auto * t = dynamic_cast(obj); assert(t); - if(t->tempOwner < PlayerColor::PLAYER_LIMIT) - gs->getPlayerState(t->tempOwner)->towns -= t; + + PlayerColor oldOwner = t->tempOwner; + if(oldOwner.isValidPlayer()) + { + auto * state = gs->getPlayerState(oldOwner); + state->towns -= t; + + if(state->towns.empty()) + *state->daysWithoutCastle = 0; + } if(val < PlayerColor::PLAYER_LIMIT_I) { PlayerState * p = gs->getPlayerState(PlayerColor(val)); @@ -2509,7 +2502,10 @@ void PlayerCheated::applyGs(CGameState * gs) const void YourTurn::applyGs(CGameState * gs) const { gs->currentPlayer = player; +} +void DaysWithoutTown::applyGs(CGameState * gs) const +{ auto & playerState = gs->players[player]; playerState.daysWithoutCastle = daysWithoutCastle; } diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 88d43967c..8c6feefd2 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -232,6 +232,7 @@ void registerTypesClientPacks1(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index b8ca99c58..9b51db83e 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -609,7 +609,28 @@ void CGameHandler::onPlayerTurnStarted(PlayerColor which) void CGameHandler::onPlayerTurnEnded(PlayerColor which) { - // 7 days without castle + const auto * playerState = gs->getPlayerState(which); + assert(playerState->status == EPlayerStatus::INGAME); + + if (playerState->towns.empty()) + { + DaysWithoutTown pack; + pack.player = which; + pack.daysWithoutCastle = playerState->daysWithoutCastle.value_or(0) + 1; + sendAndApply(&pack); + } + else + { + if (playerState->daysWithoutCastle.has_value()) + { + DaysWithoutTown pack; + pack.player = which; + pack.daysWithoutCastle = std::nullopt; + sendAndApply(&pack); + } + } + + // check for 7 days without castle checkVictoryLossConditionsForPlayer(which); bool newWeek = getDate(Date::DAY_OF_WEEK) == 7; // end of 7th day diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index d2e568273..a3b0c81b5 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -84,7 +84,7 @@ void TurnTimerHandler::onPlayerMakingTurn(PlayerState & state, int waitTime) state.turnTimer.baseTimer = 0; onPlayerMakingTurn(state, waitTime); } - else if(!gameHandler.queries->topQuery(state.color)) + else if(!gameHandler.queries->topQuery(state.color)) //wait for replies to avoid pending queries gameHandler.turnOrder->onPlayerEndsTurn(state.color); } } diff --git a/server/processors/TurnOrderProcessor.cpp b/server/processors/TurnOrderProcessor.cpp index 165d529c6..9790e59d8 100644 --- a/server/processors/TurnOrderProcessor.cpp +++ b/server/processors/TurnOrderProcessor.cpp @@ -100,8 +100,6 @@ void TurnOrderProcessor::doStartPlayerTurn(PlayerColor 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); assert(actingPlayers.size() == 1); // No simturns yet :( @@ -111,6 +109,7 @@ void TurnOrderProcessor::doStartPlayerTurn(PlayerColor which) void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which) { assert(playerMakingTurn(which)); + assert(gameHandler->getPlayerStatus(which) == EPlayerStatus::INGAME); actingPlayers.erase(which); actedPlayers.insert(which); @@ -170,7 +169,10 @@ bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which) gameHandler->onPlayerTurnEnded(which); - doEndPlayerTurn(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; } diff --git a/server/processors/TurnOrderProcessor.h b/server/processors/TurnOrderProcessor.h index ea6e7676b..c4b158704 100644 --- a/server/processors/TurnOrderProcessor.h +++ b/server/processors/TurnOrderProcessor.h @@ -37,6 +37,10 @@ class TurnOrderProcessor : boost::noncopyable void doStartPlayerTurn(PlayerColor which); void doEndPlayerTurn(PlayerColor which); + bool playerAwaitsTurn(PlayerColor which) const; + bool playerMakingTurn(PlayerColor which) const; + bool playerAwaitsNewDay(PlayerColor which) const; + public: TurnOrderProcessor(CGameHandler * owner); @@ -46,21 +50,14 @@ public: /// NetPack call-in bool onPlayerEndsTurn(PlayerColor which); + /// Ends player turn and removes this player from turn order void onPlayerEndsGame(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) + template + void serialize(Handler & h, const int version) { h & awaitingPlayers; h & actingPlayers; From 97ba7df15219db84fc6581bc541f77ee4a7795f0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 24 Aug 2023 22:02:24 +0300 Subject: [PATCH 13/14] Fix handling of map turn/day limit --- server/CGameHandler.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 9b51db83e..81650e755 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -947,6 +947,9 @@ void CGameHandler::onNewTurn() } } + if (!firstTurn) + checkVictoryLossConditionsForAll(); // check for map turn limit + logGlobal->trace("Info about turn %d has been sent!", n.day); handleTimeEvents(); //call objects From 940bdcee3ed9abdb08a2b436dcdae2abd00e0e4f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 24 Aug 2023 23:34:28 +0300 Subject: [PATCH 14/14] Fixes according to review --- lib/NetPackVisitor.h | 2 +- lib/NetPacksLib.cpp | 2 +- server/processors/TurnOrderProcessor.cpp | 10 +++++----- server/processors/TurnOrderProcessor.h | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/NetPackVisitor.h b/lib/NetPackVisitor.h index bf740e46b..44f25dc1c 100644 --- a/lib/NetPackVisitor.h +++ b/lib/NetPackVisitor.h @@ -27,7 +27,7 @@ public: virtual void visitPlayerBlocked(PlayerBlocked & pack) {} virtual void visitPlayerCheated(PlayerCheated & pack) {} virtual void visitYourTurn(YourTurn & pack) {} - virtual void visitYourTurn(DaysWithoutTown & pack) {} + virtual void visitDaysWithoutTown(DaysWithoutTown & pack) {} virtual void visitTurnTimeUpdate(TurnTimeUpdate & pack) {} virtual void visitEntitiesChanged(EntitiesChanged & pack) {} virtual void visitSetResources(SetResources & pack) {} diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index e296c2273..7a1a90463 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -110,7 +110,7 @@ void YourTurn::visitTyped(ICPackVisitor & visitor) void DaysWithoutTown::visitTyped(ICPackVisitor & visitor) { - visitor.visitYourTurn(*this); + visitor.visitDaysWithoutTown(*this); } void EntitiesChanged::visitTyped(ICPackVisitor & visitor) diff --git a/server/processors/TurnOrderProcessor.cpp b/server/processors/TurnOrderProcessor.cpp index 9790e59d8..df57a0167 100644 --- a/server/processors/TurnOrderProcessor.cpp +++ b/server/processors/TurnOrderProcessor.cpp @@ -108,7 +108,7 @@ void TurnOrderProcessor::doStartPlayerTurn(PlayerColor which) void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which) { - assert(playerMakingTurn(which)); + assert(isPlayerMakingTurn(which)); assert(gameHandler->getPlayerStatus(which) == EPlayerStatus::INGAME); actingPlayers.erase(which); @@ -149,7 +149,7 @@ void TurnOrderProcessor::onPlayerEndsGame(PlayerColor which) bool TurnOrderProcessor::onPlayerEndsTurn(PlayerColor which) { - if (!playerMakingTurn(which)) + if (!isPlayerMakingTurn(which)) { gameHandler->complain("Can not end turn for player that is not acting!"); return false; @@ -197,17 +197,17 @@ void TurnOrderProcessor::tryStartTurnsForPlayers() } } -bool TurnOrderProcessor::playerAwaitsTurn(PlayerColor which) const +bool TurnOrderProcessor::isPlayerAwaitsTurn(PlayerColor which) const { return vstd::contains(awaitingPlayers, which); } -bool TurnOrderProcessor::playerMakingTurn(PlayerColor which) const +bool TurnOrderProcessor::isPlayerMakingTurn(PlayerColor which) const { return vstd::contains(actingPlayers, which); } -bool TurnOrderProcessor::playerAwaitsNewDay(PlayerColor which) const +bool TurnOrderProcessor::isPlayerAwaitsNewDay(PlayerColor which) const { return vstd::contains(actedPlayers, which); } diff --git a/server/processors/TurnOrderProcessor.h b/server/processors/TurnOrderProcessor.h index c4b158704..af91bc547 100644 --- a/server/processors/TurnOrderProcessor.h +++ b/server/processors/TurnOrderProcessor.h @@ -37,9 +37,9 @@ class TurnOrderProcessor : boost::noncopyable void doStartPlayerTurn(PlayerColor which); void doEndPlayerTurn(PlayerColor which); - bool playerAwaitsTurn(PlayerColor which) const; - bool playerMakingTurn(PlayerColor which) const; - bool playerAwaitsNewDay(PlayerColor which) const; + bool isPlayerAwaitsTurn(PlayerColor which) const; + bool isPlayerMakingTurn(PlayerColor which) const; + bool isPlayerAwaitsNewDay(PlayerColor which) const; public: TurnOrderProcessor(CGameHandler * owner);