From 6226ddf4a72c66c915017edabd4ef85164fadcad Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 28 Aug 2023 01:10:04 +0400 Subject: [PATCH 01/18] Fix potential concurrenccy issues --- server/TurnTimerHandler.cpp | 9 +++++++++ server/TurnTimerHandler.h | 1 + 2 files changed, 10 insertions(+) diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index d3aca09d9..ea621bef2 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -32,6 +32,7 @@ void TurnTimerHandler::onGameplayStart(PlayerColor player) { if(si->turnTimerInfo.isEnabled()) { + std::lock_guard guard(mx); timers[player] = si->turnTimerInfo; timers[player].turnTimer = 0; } @@ -44,6 +45,7 @@ void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) { if(si->turnTimerInfo.isEnabled()) { + std::lock_guard guard(mx); timers[player].baseTimer += timers[player].turnTimer; timers[player].turnTimer = si->turnTimerInfo.turnTimer; @@ -66,6 +68,7 @@ void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime) if(state.human && si->turnTimerInfo.isEnabled() && !gs->curB) { + std::lock_guard guard(mx); if(timers[player].turnTimer > 0) { timers[player].turnTimer -= waitTime; @@ -98,6 +101,8 @@ void TurnTimerHandler::onBattleStart() if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled()) return; + std::lock_guard guard(mx); + auto attacker = gs->curB->getSidePlayer(BattleSide::ATTACKER); auto defender = gs->curB->getSidePlayer(BattleSide::DEFENDER); @@ -123,6 +128,8 @@ void TurnTimerHandler::onBattleNextStack(const CStack & stack) if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled()) return; + std::lock_guard guard(mx); + auto player = stack.getOwner(); if(!player.isValidPlayer()) @@ -145,6 +152,8 @@ void TurnTimerHandler::onBattleLoop(int waitTime) if(!si || !gs || !gs->curB) return; + std::lock_guard guard(mx); + const auto * stack = gs->curB.get()->battleGetStackByID(gs->curB->getActiveStackID()); if(!stack || !stack->getOwner().isValidPlayer()) return; diff --git a/server/TurnTimerHandler.h b/server/TurnTimerHandler.h index d805905a4..649cf206c 100644 --- a/server/TurnTimerHandler.h +++ b/server/TurnTimerHandler.h @@ -27,6 +27,7 @@ class TurnTimerHandler const int turnTimePropagateFrequencyCrit = 1000; const int turnTimePropagateThreshold = 3000; std::map timers; + std::recursive_mutex mx; public: TurnTimerHandler(CGameHandler &); From 4ee47b01aeca8efa0568dddef29f0e850d16c598 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 28 Aug 2023 01:59:47 +0400 Subject: [PATCH 02/18] Make possible to play with different timers independently --- client/adventureMap/AdventureMapInterface.cpp | 2 +- lib/TurnTimerInfo.cpp | 4 ++-- server/TurnTimerHandler.cpp | 9 +++------ 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/client/adventureMap/AdventureMapInterface.cpp b/client/adventureMap/AdventureMapInterface.cpp index 175bf25e4..50f20c376 100644 --- a/client/adventureMap/AdventureMapInterface.cpp +++ b/client/adventureMap/AdventureMapInterface.cpp @@ -63,7 +63,7 @@ AdventureMapInterface::AdventureMapInterface(): shortcuts->setState(EAdventureState::MAKING_TURN); widget->getMapView()->onViewMapActivated(); - if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.isEnabled()) + if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.isEnabled() || LOCPLINT->cb->getStartInfo()->turnTimerInfo.isBattleEnabled()) watches = std::make_shared(); addUsedEvents(KEYBOARD | TIME); diff --git a/lib/TurnTimerInfo.cpp b/lib/TurnTimerInfo.cpp index 6d10f67ee..0594fad65 100644 --- a/lib/TurnTimerInfo.cpp +++ b/lib/TurnTimerInfo.cpp @@ -14,12 +14,12 @@ VCMI_LIB_NAMESPACE_BEGIN bool TurnTimerInfo::isEnabled() const { - return turnTimer > 0; + return turnTimer > 0 || baseTimer > 0; } bool TurnTimerInfo::isBattleEnabled() const { - return creatureTimer > 0; + return creatureTimer > 0 || battleTimer > 0; } VCMI_LIB_NAMESPACE_END diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index ea621bef2..de931ec51 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -30,12 +30,9 @@ void TurnTimerHandler::onGameplayStart(PlayerColor player) { if(const auto * si = gameHandler.getStartInfo()) { - if(si->turnTimerInfo.isEnabled()) - { - std::lock_guard guard(mx); - timers[player] = si->turnTimerInfo; - timers[player].turnTimer = 0; - } + std::lock_guard guard(mx); + timers[player] = si->turnTimerInfo; + timers[player].turnTimer = 0; } } From 7dc1717ec699a092c7370b85f656871a6aa1b233 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 28 Aug 2023 02:00:02 +0400 Subject: [PATCH 03/18] Handle timer for tactic phase --- server/TurnTimerHandler.cpp | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index de931ec51..b5d86827b 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -151,11 +151,21 @@ void TurnTimerHandler::onBattleLoop(int waitTime) std::lock_guard guard(mx); - const auto * stack = gs->curB.get()->battleGetStackByID(gs->curB->getActiveStackID()); - if(!stack || !stack->getOwner().isValidPlayer()) - return; + ui8 side = 0; + const CStack * stack = nullptr; + bool isTactisPhase = gs->curB.get()->battleTacticDist() > 0; - auto & state = gs->players.at(gs->curB->getSidePlayer(stack->unitSide())); + if(isTactisPhase) + side = gs->curB.get()->battleGetTacticsSide(); + else + { + stack = gs->curB.get()->battleGetStackByID(gs->curB->getActiveStackID()); + if(!stack || !stack->getOwner().isValidPlayer()) + return; + side = stack->unitSide(); + } + + auto & state = gs->players.at(gs->curB->getSidePlayer(side)); auto turnTimerUpdateApplier = [&](TurnTimerInfo & tTimer, int waitTime) { @@ -192,9 +202,14 @@ void TurnTimerHandler::onBattleLoop(int waitTime) else { BattleAction doNothing; - doNothing.actionType = EActionType::DEFEND; - doNothing.side = stack->unitSide(); - doNothing.stackNumber = stack->unitId(); + doNothing.side = side; + if(isTactisPhase) + doNothing.actionType = EActionType::END_TACTIC_PHASE; + else + { + doNothing.actionType = EActionType::DEFEND; + doNothing.stackNumber = stack->unitId(); + } gameHandler.battles->makePlayerBattleAction(state.color, doNothing); } } From 2c61d1b23f628d89e2c6a44a4c878e21f11749c2 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 28 Aug 2023 03:14:58 +0400 Subject: [PATCH 04/18] Use precision clocks for timer --- server/CGameHandler.cpp | 11 ++++++++--- server/TurnTimerHandler.cpp | 6 +++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4e8d1b62e..b7371bd4f 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -999,18 +999,23 @@ void CGameHandler::run(bool resume) turnOrder->onGameStarted(); //wait till game is done + auto clockLast = std::chrono::high_resolution_clock::now(); while(lobby->getState() == EServerState::GAMEPLAY) { + const auto clockDuration = std::chrono::high_resolution_clock::now() - clockLast; + const int timePassed = std::chrono::duration_cast(clockDuration).count(); + clockLast += clockDuration; + const int waitTime = 100; //ms for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) if(gs->isPlayerMakingTurn(player)) - turnTimerHandler.onPlayerMakingTurn(player, waitTime); + turnTimerHandler.onPlayerMakingTurn(player, timePassed); if(gs->curB) - turnTimerHandler.onBattleLoop(waitTime); + turnTimerHandler.onBattleLoop(timePassed); - boost::this_thread::sleep_for(boost::chrono::milliseconds(waitTime)); + boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); } } diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index b5d86827b..6db8dbb17 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -72,7 +72,7 @@ void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime) int frequency = (timers[player].turnTimer > turnTimePropagateThreshold ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit); if(state.status == EPlayerStatus::INGAME //do not send message if player is not active already - && timers[player].turnTimer % frequency == 0) + && timers[player].turnTimer / 100 * 100 % frequency == 0) { TurnTimeUpdate ttu; ttu.player = state.color; @@ -132,7 +132,7 @@ void TurnTimerHandler::onBattleNextStack(const CStack & stack) if(!player.isValidPlayer()) return; - if(timers[player].battleTimer < si->turnTimerInfo.battleTimer) + if(timers[player].battleTimer == 0) timers[player].battleTimer = timers[player].creatureTimer; timers[player].creatureTimer = si->turnTimerInfo.creatureTimer; @@ -177,7 +177,7 @@ void TurnTimerHandler::onBattleLoop(int waitTime) ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit; if(state.status == EPlayerStatus::INGAME //do not send message if player is not active already - && tTimer.creatureTimer % frequency == 0) + && (tTimer.creatureTimer / 100 * 100 % frequency) == 0) { TurnTimeUpdate ttu; ttu.player = state.color; From 15f57a08177e435d01f1855b6a0020e288ab61c7 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 28 Aug 2023 04:40:35 +0400 Subject: [PATCH 05/18] Timer enabler --- server/TurnTimerHandler.cpp | 51 +++++++++++++++++++++---------------- server/TurnTimerHandler.h | 2 ++ 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index 6db8dbb17..f1f51eac6 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -33,9 +33,17 @@ void TurnTimerHandler::onGameplayStart(PlayerColor player) std::lock_guard guard(mx); timers[player] = si->turnTimerInfo; timers[player].turnTimer = 0; + timerEnabled[player] = true; } } +void TurnTimerHandler::setTimerEnabled(PlayerColor player, bool enabled) +{ + std::lock_guard guard(mx); + assert(player.isValidPlayer()); + timerEnabled[player] = enabled; +} + void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) { if(const auto * si = gameHandler.getStartInfo()) @@ -58,14 +66,15 @@ void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime) { const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); - if(!si || !gs) + if(!si || !gs || gs->curB || !si->turnTimerInfo.isEnabled()) return; + std::lock_guard guard(mx); + auto & state = gs->players.at(player); - if(state.human && si->turnTimerInfo.isEnabled() && !gs->curB) + if(state.human && timerEnabled[player]) { - std::lock_guard guard(mx); if(timers[player].turnTimer > 0) { timers[player].turnTimer -= waitTime; @@ -146,7 +155,7 @@ void TurnTimerHandler::onBattleLoop(int waitTime) { const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); - if(!si || !gs || !gs->curB) + if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled()) return; std::lock_guard guard(mx); @@ -189,29 +198,27 @@ void TurnTimerHandler::onBattleLoop(int waitTime) return false; }; - if(state.human && si->turnTimerInfo.isBattleEnabled()) + if(state.human && timerEnabled[state.color] + && !turnTimerUpdateApplier(timers[state.color], waitTime)) { - if(!turnTimerUpdateApplier(timers[state.color], waitTime)) + if(timers[state.color].battleTimer > 0) { - if(timers[state.color].battleTimer > 0) - { - timers[state.color].creatureTimer = timers[state.color].battleTimer; - timers[state.color].battleTimer = 0; - turnTimerUpdateApplier(timers[state.color], 0); - } + timers[state.color].creatureTimer = timers[state.color].battleTimer; + timers[state.color].battleTimer = 0; + turnTimerUpdateApplier(timers[state.color], 0); + } + else + { + BattleAction doNothing; + doNothing.side = side; + if(isTactisPhase) + doNothing.actionType = EActionType::END_TACTIC_PHASE; else { - BattleAction doNothing; - doNothing.side = side; - if(isTactisPhase) - doNothing.actionType = EActionType::END_TACTIC_PHASE; - else - { - doNothing.actionType = EActionType::DEFEND; - doNothing.stackNumber = stack->unitId(); - } - gameHandler.battles->makePlayerBattleAction(state.color, doNothing); + doNothing.actionType = EActionType::DEFEND; + doNothing.stackNumber = stack->unitId(); } + gameHandler.battles->makePlayerBattleAction(state.color, doNothing); } } } diff --git a/server/TurnTimerHandler.h b/server/TurnTimerHandler.h index 649cf206c..c5b201435 100644 --- a/server/TurnTimerHandler.h +++ b/server/TurnTimerHandler.h @@ -27,6 +27,7 @@ class TurnTimerHandler const int turnTimePropagateFrequencyCrit = 1000; const int turnTimePropagateThreshold = 3000; std::map timers; + std::map timerEnabled; std::recursive_mutex mx; public: @@ -38,4 +39,5 @@ public: void onBattleStart(); void onBattleNextStack(const CStack & stack); void onBattleLoop(int waitTime); + void setTimerEnabled(PlayerColor player, bool enabled); }; From 68b11dbe3845ad37cebfcf96d044e88e880cff89 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 28 Aug 2023 04:45:35 +0400 Subject: [PATCH 06/18] Fix no-base turn timer --- server/TurnTimerHandler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index f1f51eac6..1f592ee67 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -51,7 +51,8 @@ void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) if(si->turnTimerInfo.isEnabled()) { std::lock_guard guard(mx); - timers[player].baseTimer += timers[player].turnTimer; + if(si->turnTimerInfo.baseTimer > 0) + timers[player].baseTimer += timers[player].turnTimer; timers[player].turnTimer = si->turnTimerInfo.turnTimer; TurnTimeUpdate ttu; From 21af69e0b7ada6271bf770fa2857e62c1cc9365a Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 28 Aug 2023 04:52:02 +0400 Subject: [PATCH 07/18] Fix timer synchornization --- server/TurnTimerHandler.cpp | 12 ++++++++++-- server/TurnTimerHandler.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index 1f592ee67..495aca009 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -34,6 +34,7 @@ void TurnTimerHandler::onGameplayStart(PlayerColor player) timers[player] = si->turnTimerInfo; timers[player].turnTimer = 0; timerEnabled[player] = true; + timerLastUpdate[player] = std::numeric_limits::max(); } } @@ -59,6 +60,7 @@ void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) ttu.player = player; ttu.turnTimer = timers[player]; gameHandler.sendAndApply(&ttu); + timerLastUpdate[player] = 0; } } } @@ -79,15 +81,17 @@ void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime) if(timers[player].turnTimer > 0) { timers[player].turnTimer -= waitTime; + timerLastUpdate[player] += waitTime; int frequency = (timers[player].turnTimer > turnTimePropagateThreshold ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit); if(state.status == EPlayerStatus::INGAME //do not send message if player is not active already - && timers[player].turnTimer / 100 * 100 % frequency == 0) + && timerLastUpdate[player] >= frequency) { TurnTimeUpdate ttu; ttu.player = state.color; ttu.turnTimer = timers[player]; gameHandler.sendAndApply(&ttu); + timerLastUpdate[player] = 0; } } else if(timers[player].baseTimer > 0) @@ -124,6 +128,7 @@ void TurnTimerHandler::onBattleStart() ttu.player = i; ttu.turnTimer = timers[i]; gameHandler.sendAndApply(&ttu); + timerLastUpdate[i] = 0; } } } @@ -150,6 +155,7 @@ void TurnTimerHandler::onBattleNextStack(const CStack & stack) ttu.player = player; ttu.turnTimer = timers[player]; gameHandler.sendAndApply(&ttu); + timerLastUpdate[player] = 0; } void TurnTimerHandler::onBattleLoop(int waitTime) @@ -182,17 +188,19 @@ void TurnTimerHandler::onBattleLoop(int waitTime) if(tTimer.creatureTimer > 0) { tTimer.creatureTimer -= waitTime; + timerLastUpdate[state.color] += waitTime; int frequency = (tTimer.creatureTimer > turnTimePropagateThreshold && si->turnTimerInfo.creatureTimer - tTimer.creatureTimer > turnTimePropagateThreshold) ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit; if(state.status == EPlayerStatus::INGAME //do not send message if player is not active already - && (tTimer.creatureTimer / 100 * 100 % frequency) == 0) + && timerLastUpdate[state.color] >= frequency) { TurnTimeUpdate ttu; ttu.player = state.color; ttu.turnTimer = tTimer; gameHandler.sendAndApply(&ttu); + timerLastUpdate[state.color] = 0; } return true; } diff --git a/server/TurnTimerHandler.h b/server/TurnTimerHandler.h index c5b201435..367898325 100644 --- a/server/TurnTimerHandler.h +++ b/server/TurnTimerHandler.h @@ -28,6 +28,7 @@ class TurnTimerHandler const int turnTimePropagateThreshold = 3000; std::map timers; std::map timerEnabled; + std::map timerLastUpdate; std::recursive_mutex mx; public: From b5417c667c42fee1099f3cd5053b267ce2f3edb8 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 28 Aug 2023 05:06:43 +0400 Subject: [PATCH 08/18] Hold timer client counter --- client/CPlayerInterface.cpp | 10 +++++++++- client/CPlayerInterface.h | 3 +++ client/adventureMap/TurnTimerWidget.cpp | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index ce4f4a419..fa035e9e2 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -140,7 +140,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player): firstCall = 1; //if loading will be overwritten in serialize autosaveCount = 0; isAutoFightOn = false; - + timerEnabled = true; duringMovement = false; ignoreEvents = false; numOfMovedArts = 0; @@ -272,6 +272,8 @@ void CPlayerInterface::yourTurn(QueryID queryID) makingTurn = true; adventureInt->onPlayerTurnStarted(playerID); } + + timerEnabled = false; } acceptTurn(queryID); } @@ -324,6 +326,7 @@ void CPlayerInterface::acceptTurn(QueryID queryID) } cb->selectionMade(0, queryID); + timerEnabled = true; } void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose) @@ -2124,3 +2127,8 @@ std::optional CPlayerInterface::makeSurrenderRetreatDecision(const { return std::nullopt; } + +bool CPlayerInterface::isTimerEnabled() const +{ + return timerEnabled; +} diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index c5048637f..157158a71 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -57,6 +57,7 @@ namespace boost /// Central class for managing user interface logic class CPlayerInterface : public CGameInterface, public IUpdateable { + bool timerEnabled; bool duringMovement; bool ignoreEvents; size_t numOfMovedArts; @@ -206,6 +207,8 @@ public: // public interface for use by client via LOCPLINT access ///returns true if all events are processed internally bool capturedAllEvents(); + + bool isTimerEnabled() const; CPlayerInterface(PlayerColor Player); ~CPlayerInterface(); diff --git a/client/adventureMap/TurnTimerWidget.cpp b/client/adventureMap/TurnTimerWidget.cpp index 68428ad03..a66587a87 100644 --- a/client/adventureMap/TurnTimerWidget.cpp +++ b/client/adventureMap/TurnTimerWidget.cpp @@ -114,7 +114,8 @@ void TurnTimerWidget::tick(uint32_t msPassed) continue; auto time = LOCPLINT->cb->getPlayerTurnTime(player); - cachedTurnTime -= msPassed; + if(LOCPLINT->isTimerEnabled()) + cachedTurnTime -= msPassed; if(cachedTurnTime < 0) cachedTurnTime = 0; //do not go below zero if(lastPlayer != player) From 432ed18579f59128175c5e2e67446570dc266c8a Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 28 Aug 2023 15:09:15 +0400 Subject: [PATCH 09/18] Fix overlap of timer and fps widgets --- client/gui/CGuiHandler.cpp | 4 ++-- config/widgets/turnTimer.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index c6bb020fd..504f404ad 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -184,11 +184,11 @@ Point CGuiHandler::screenDimensions() const void CGuiHandler::drawFPSCounter() { - static SDL_Rect overlay = { 0, 0, 64, 32}; + static SDL_Rect overlay = { 0, 0, 24, 24}; uint32_t black = SDL_MapRGB(screen->format, 10, 10, 10); SDL_FillRect(screen, &overlay, black); std::string fps = std::to_string(framerate().getFramerate()); - graphics->fonts[FONT_BIG]->renderTextLeft(screen, fps, Colors::YELLOW, Point(10, 10)); + graphics->fonts[FONT_BIG]->renderTextLeft(screen, fps, Colors::YELLOW, Point(4, 2)); } bool CGuiHandler::amIGuiThread() diff --git a/config/widgets/turnTimer.json b/config/widgets/turnTimer.json index c2c8b01cf..5965ed2f0 100644 --- a/config/widgets/turnTimer.json +++ b/config/widgets/turnTimer.json @@ -3,7 +3,7 @@ [ { //backgound color "type": "drawRect", - "rect": {"x": 4, "y": 4, "w": 68, "h": 24}, + "rect": {"x": 4, "y": 4, "w": 72, "h": 24}, "color": [10, 10, 10, 255] }, @@ -21,7 +21,7 @@ "alignment": "left", "color": "yellow", "text": "", - "position": {"x": 24, "y": 2} + "position": {"x": 26, "y": 2} }, ], From 883c68b1512ff79aac7d03bf185dc019ec6def04 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Mon, 28 Aug 2023 19:57:42 +0400 Subject: [PATCH 10/18] Remove unused variable --- server/CGameHandler.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index b7371bd4f..b97ef40e3 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1006,8 +1006,6 @@ void CGameHandler::run(bool resume) const int timePassed = std::chrono::duration_cast(clockDuration).count(); clockLast += clockDuration; - const int waitTime = 100; //ms - for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) if(gs->isPlayerMakingTurn(player)) turnTimerHandler.onPlayerMakingTurn(player, timePassed); From c8243313b8030407037a960febf39b03e7917048 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Tue, 29 Aug 2023 14:53:15 +0400 Subject: [PATCH 11/18] Translations for turn timers --- Mods/vcmi/config/vcmi/english.json | 9 +++++++++ client/gui/InterfaceObjectConfigurable.cpp | 6 ++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 8ef48c372..cb7c2489f 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -191,6 +191,15 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Team Alignments", "vcmi.randomMapTab.widgets.roadTypesLabel" : "Road Types", + "vcmi.optionsTab.widgets.chessFieldBase.help" : "{Base timer}\n\nCounts down when {turn timer} runs out, never resets. If timer expires, player ends turn.", + "vcmi.optionsTab.widgets.chessFieldTurn.help" : "{Turn timer}\n\nCounts down when player is making turn on adventure map, resets every turn. If {base timer} is enabled, unspent time will be added to it.", + "vcmi.optionsTab.widgets.chessFieldBattle.help" : "{Battle timer}\n\nCounts down during battles when {stack timer} runs out, resets every battle. if timer expires, currently acting stack will defend.", + "vcmi.optionsTab.widgets.chessFieldCreature.help" : "{Creature timer}\n\nCounts down when player is choosing action in battle, resets after each stack turn.", + "vcmi.optionsTab.widgets.labelTimer" : "Timer", + "vcmi.optionsTab.widgets.timerModeSwitch.classic" : "Classic timer", + "vcmi.optionsTab.widgets.timerModeSwitch.chess" : "Chess timer", + + // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "The enemy has managed to survive till this day. Victory is theirs!", "vcmi.map.victoryCondition.daysPassed.toSelf" : "Congratulations! You have managed to survive. Victory is yours!", diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index bab69dd1d..65da83227 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -157,6 +157,8 @@ std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const return ""; std::string s = config.String(); + if(s.empty()) + return s; logGlobal->debug("Reading text from translations by key: %s", s); return CGI->generaltexth->translate(s); } @@ -561,8 +563,8 @@ std::shared_ptr InterfaceObjectConfigurable::buildTextInput(const Js result->font = readFont(config["font"]); if(!config["color"].isNull()) result->setColor(readColor(config["color"])); - if(!config["text"].isNull()) - result->setText(readText(config["text"])); + if(!config["text"].isNull() && config["text"].isString()) + result->setText(config["text"].String()); //for input field raw string is taken if(!config["callback"].isNull()) result->cb += callbacks_string.at(config["callback"].String()); if(!config["help"].isNull()) From 03496e673866ace0c20913195256c5fecc98fce2 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Tue, 29 Aug 2023 15:48:42 +0400 Subject: [PATCH 12/18] Code review fixes --- server/CGameHandler.cpp | 13 +++---------- server/TurnTimerHandler.cpp | 30 ++++++++++++++++++++---------- server/TurnTimerHandler.h | 6 ++++-- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index b97ef40e3..cbe6505b5 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -999,20 +999,13 @@ void CGameHandler::run(bool resume) turnOrder->onGameStarted(); //wait till game is done - auto clockLast = std::chrono::high_resolution_clock::now(); + auto clockLast = std::chrono::steady_clock::now(); while(lobby->getState() == EServerState::GAMEPLAY) { - const auto clockDuration = std::chrono::high_resolution_clock::now() - clockLast; + const auto clockDuration = std::chrono::steady_clock::now() - clockLast; const int timePassed = std::chrono::duration_cast(clockDuration).count(); clockLast += clockDuration; - - for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) - if(gs->isPlayerMakingTurn(player)) - turnTimerHandler.onPlayerMakingTurn(player, timePassed); - - if(gs->curB) - turnTimerHandler.onBattleLoop(timePassed); - + turnTimerHandler.update(timePassed); boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); } } diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index 495aca009..6020de0be 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -28,9 +28,9 @@ TurnTimerHandler::TurnTimerHandler(CGameHandler & gh): void TurnTimerHandler::onGameplayStart(PlayerColor player) { + std::lock_guard guard(mx); if(const auto * si = gameHandler.getStartInfo()) { - std::lock_guard guard(mx); timers[player] = si->turnTimerInfo; timers[player].turnTimer = 0; timerEnabled[player] = true; @@ -47,11 +47,11 @@ void TurnTimerHandler::setTimerEnabled(PlayerColor player, bool enabled) void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) { + std::lock_guard guard(mx); if(const auto * si = gameHandler.getStartInfo()) { if(si->turnTimerInfo.isEnabled()) { - std::lock_guard guard(mx); if(si->turnTimerInfo.baseTimer > 0) timers[player].baseTimer += timers[player].turnTimer; timers[player].turnTimer = si->turnTimerInfo.turnTimer; @@ -65,15 +65,28 @@ void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) } } +void TurnTimerHandler::update(int waitTime) +{ + std::lock_guard guard(mx); + if(const auto * gs = gameHandler.gameState()) + { + for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) + if(gs->isPlayerMakingTurn(player)) + onPlayerMakingTurn(player, waitTime); + + if(gs->curB) + onBattleLoop(waitTime); + } +} + void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime) { + std::lock_guard guard(mx); const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs || gs->curB || !si->turnTimerInfo.isEnabled()) return; - std::lock_guard guard(mx); - auto & state = gs->players.at(player); if(state.human && timerEnabled[player]) @@ -107,12 +120,11 @@ void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime) void TurnTimerHandler::onBattleStart() { + std::lock_guard guard(mx); const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled()) return; - - std::lock_guard guard(mx); auto attacker = gs->curB->getSidePlayer(BattleSide::ATTACKER); auto defender = gs->curB->getSidePlayer(BattleSide::DEFENDER); @@ -135,13 +147,12 @@ void TurnTimerHandler::onBattleStart() void TurnTimerHandler::onBattleNextStack(const CStack & stack) { + std::lock_guard guard(mx); const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled()) return; - std::lock_guard guard(mx); - auto player = stack.getOwner(); if(!player.isValidPlayer()) @@ -160,13 +171,12 @@ void TurnTimerHandler::onBattleNextStack(const CStack & stack) void TurnTimerHandler::onBattleLoop(int waitTime) { + std::lock_guard guard(mx); const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled()) return; - std::lock_guard guard(mx); - ui8 side = 0; const CStack * stack = nullptr; bool isTactisPhase = gs->curB.get()->battleTacticDist() > 0; diff --git a/server/TurnTimerHandler.h b/server/TurnTimerHandler.h index 367898325..572831da3 100644 --- a/server/TurnTimerHandler.h +++ b/server/TurnTimerHandler.h @@ -31,14 +31,16 @@ class TurnTimerHandler std::map timerLastUpdate; std::recursive_mutex mx; + void onPlayerMakingTurn(PlayerColor player, int waitTime); + void onBattleLoop(int waitTime); + public: TurnTimerHandler(CGameHandler &); void onGameplayStart(PlayerColor player); void onPlayerGetTurn(PlayerColor player); - void onPlayerMakingTurn(PlayerColor player, int waitTime); void onBattleStart(); void onBattleNextStack(const CStack & stack); - void onBattleLoop(int waitTime); + void update(int waitTime); void setTimerEnabled(PlayerColor player, bool enabled); }; From b4c20734f37ba743d015afa5fdb5fdc78d58c9af Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 30 Aug 2023 02:17:29 +0400 Subject: [PATCH 13/18] Support timer logic for pve and pvp --- client/adventureMap/TurnTimerWidget.cpp | 3 +- server/TurnTimerHandler.cpp | 250 ++++++++++++++--------- server/TurnTimerHandler.h | 20 +- server/battles/BattleResultProcessor.cpp | 4 +- 4 files changed, 177 insertions(+), 100 deletions(-) diff --git a/client/adventureMap/TurnTimerWidget.cpp b/client/adventureMap/TurnTimerWidget.cpp index a66587a87..88b1ccd08 100644 --- a/client/adventureMap/TurnTimerWidget.cpp +++ b/client/adventureMap/TurnTimerWidget.cpp @@ -110,7 +110,8 @@ void TurnTimerWidget::tick(uint32_t msPassed) if(auto * stack = LOCPLINT->battleInt->stacksController->getActiveStack()) player = stack->getOwner(); } - else if (!LOCPLINT->cb->isPlayerMakingTurn(player)) + + if(p != player || !LOCPLINT->cb->isPlayerMakingTurn(player)) continue; auto time = LOCPLINT->cb->getPlayerTurnTime(player); diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index 6020de0be..936b2926b 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -31,10 +31,11 @@ void TurnTimerHandler::onGameplayStart(PlayerColor player) std::lock_guard guard(mx); if(const auto * si = gameHandler.getStartInfo()) { - timers[player] = si->turnTimerInfo; - timers[player].turnTimer = 0; - timerEnabled[player] = true; - timerLastUpdate[player] = std::numeric_limits::max(); + timerInfo[player].timer = si->turnTimerInfo; + timerInfo[player].timer.turnTimer = 0; + timerInfo[player].isEnabled = true; + timerInfo[player].isBattle = false; + timerInfo[player].lastUpdate = std::numeric_limits::max(); } } @@ -42,7 +43,17 @@ void TurnTimerHandler::setTimerEnabled(PlayerColor player, bool enabled) { std::lock_guard guard(mx); assert(player.isValidPlayer()); - timerEnabled[player] = enabled; + timerInfo[player].isEnabled = enabled; +} + +void TurnTimerHandler::sendTimerUpdate(PlayerColor player) +{ + auto & info = timerInfo[player]; + TurnTimeUpdate ttu; + ttu.player = player; + ttu.turnTimer = info.timer; + gameHandler.sendAndApply(&ttu); + info.lastUpdate = 0; } void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) @@ -52,15 +63,12 @@ void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) { if(si->turnTimerInfo.isEnabled()) { + auto & info = timerInfo[player]; if(si->turnTimerInfo.baseTimer > 0) - timers[player].baseTimer += timers[player].turnTimer; - timers[player].turnTimer = si->turnTimerInfo.turnTimer; + info.timer.baseTimer += info.timer.turnTimer; + info.timer.turnTimer = si->turnTimerInfo.turnTimer; - TurnTimeUpdate ttu; - ttu.player = player; - ttu.turnTimer = timers[player]; - gameHandler.sendAndApply(&ttu); - timerLastUpdate[player] = 0; + sendTimerUpdate(player); } } } @@ -79,45 +87,66 @@ void TurnTimerHandler::update(int waitTime) } } +bool TurnTimerHandler::timerCountDown(int & timer, int initialTimer, PlayerColor player, int waitTime) +{ + if(timer > 0) + { + auto & info = timerInfo[player]; + timer -= waitTime; + info.lastUpdate += waitTime; + int frequency = (timer > turnTimePropagateThreshold + && initialTimer - timer > turnTimePropagateThreshold) + ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit; + + if(info.lastUpdate >= frequency) + sendTimerUpdate(player); + + return true; + } + return false; +} + void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime) { std::lock_guard guard(mx); const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); - if(!si || !gs || gs->curB || !si->turnTimerInfo.isEnabled()) + if(!si || !gs || !si->turnTimerInfo.isEnabled()) return; - auto & state = gs->players.at(player); - - if(state.human && timerEnabled[player]) + auto & info = timerInfo[player]; + const auto * state = gameHandler.getPlayerState(player); + if(state && state->human && info.isEnabled && !info.isBattle && state->status == EPlayerStatus::INGAME) { - if(timers[player].turnTimer > 0) + if(!timerCountDown(info.timer.turnTimer, si->turnTimerInfo.turnTimer, player, waitTime)) { - timers[player].turnTimer -= waitTime; - timerLastUpdate[player] += waitTime; - int frequency = (timers[player].turnTimer > turnTimePropagateThreshold ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit); - - if(state.status == EPlayerStatus::INGAME //do not send message if player is not active already - && timerLastUpdate[player] >= frequency) + if(info.timer.baseTimer > 0) { - TurnTimeUpdate ttu; - ttu.player = state.color; - ttu.turnTimer = timers[player]; - gameHandler.sendAndApply(&ttu); - timerLastUpdate[player] = 0; + info.timer.turnTimer = info.timer.baseTimer; + info.timer.baseTimer = 0; + onPlayerMakingTurn(player, 0); } + else if(!gameHandler.queries->topQuery(state->color)) //wait for replies to avoid pending queries + gameHandler.turnOrder->onPlayerEndsTurn(state->color); } - else if(timers[player].baseTimer > 0) - { - timers[player].turnTimer = timers[player].baseTimer; - timers[player].baseTimer = 0; - onPlayerMakingTurn(player, 0); - } - else if(!gameHandler.queries->topQuery(state.color)) //wait for replies to avoid pending queries - gameHandler.turnOrder->onPlayerEndsTurn(state.color); } } +bool TurnTimerHandler::isPvpBattle() const +{ + const auto * gs = gameHandler.gameState(); + auto attacker = gs->curB->getSidePlayer(BattleSide::ATTACKER); + auto defender = gs->curB->getSidePlayer(BattleSide::DEFENDER); + if(attacker.isValidPlayer() && defender.isValidPlayer()) + { + const auto * attackerState = gameHandler.getPlayerState(attacker); + const auto * defenderState = gameHandler.getPlayerState(defender); + if(attackerState && defenderState && attackerState->human && defenderState->human) + return true; + } + return false; +} + void TurnTimerHandler::onBattleStart() { std::lock_guard guard(mx); @@ -129,18 +158,47 @@ void TurnTimerHandler::onBattleStart() auto attacker = gs->curB->getSidePlayer(BattleSide::ATTACKER); auto defender = gs->curB->getSidePlayer(BattleSide::DEFENDER); + bool pvpBattle = isPvpBattle(); + for(auto i : {attacker, defender}) { if(i.isValidPlayer()) { - timers[i].battleTimer = si->turnTimerInfo.battleTimer; - timers[i].creatureTimer = si->turnTimerInfo.creatureTimer; + auto & info = timerInfo[i]; + info.isBattle = true; + info.timer.battleTimer = (pvpBattle ? si->turnTimerInfo.battleTimer : 0); + info.timer.creatureTimer = (pvpBattle ? si->turnTimerInfo.creatureTimer : si->turnTimerInfo.battleTimer); - TurnTimeUpdate ttu; - ttu.player = i; - ttu.turnTimer = timers[i]; - gameHandler.sendAndApply(&ttu); - timerLastUpdate[i] = 0; + sendTimerUpdate(i); + } + } +} + +void TurnTimerHandler::onBattleEnd() +{ + std::lock_guard guard(mx); + const auto * gs = gameHandler.gameState(); + const auto * si = gameHandler.getStartInfo(); + if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled()) + return; + + auto attacker = gs->curB->getSidePlayer(BattleSide::ATTACKER); + auto defender = gs->curB->getSidePlayer(BattleSide::DEFENDER); + + bool pvpBattle = isPvpBattle(); + + for(auto i : {attacker, defender}) + { + if(i.isValidPlayer() && !pvpBattle) + { + auto & info = timerInfo[i]; + info.isBattle = false; + if(si->turnTimerInfo.baseTimer && info.timer.baseTimer == 0) + info.timer.baseTimer = info.timer.creatureTimer; + else if(si->turnTimerInfo.turnTimer && info.timer.turnTimer == 0) + info.timer.turnTimer = info.timer.creatureTimer; + + sendTimerUpdate(i); } } } @@ -153,20 +211,17 @@ void TurnTimerHandler::onBattleNextStack(const CStack & stack) if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled()) return; - auto player = stack.getOwner(); - - if(!player.isValidPlayer()) - return; + if(isPvpBattle()) + { + auto player = stack.getOwner(); - if(timers[player].battleTimer == 0) - timers[player].battleTimer = timers[player].creatureTimer; - timers[player].creatureTimer = si->turnTimerInfo.creatureTimer; + auto & info = timerInfo[player]; + if(info.timer.battleTimer == 0) + info.timer.battleTimer = info.timer.creatureTimer; + info.timer.creatureTimer = si->turnTimerInfo.creatureTimer; - TurnTimeUpdate ttu; - ttu.player = player; - ttu.turnTimer = timers[player]; - gameHandler.sendAndApply(&ttu); - timerLastUpdate[player] = 0; + sendTimerUpdate(player); + } } void TurnTimerHandler::onBattleLoop(int waitTime) @@ -179,65 +234,72 @@ void TurnTimerHandler::onBattleLoop(int waitTime) ui8 side = 0; const CStack * stack = nullptr; - bool isTactisPhase = gs->curB.get()->battleTacticDist() > 0; + bool isTactisPhase = gs->curB->battleTacticDist() > 0; if(isTactisPhase) - side = gs->curB.get()->battleGetTacticsSide(); + side = gs->curB->battleGetTacticsSide(); else { - stack = gs->curB.get()->battleGetStackByID(gs->curB->getActiveStackID()); + stack = gs->curB->battleGetStackByID(gs->curB->getActiveStackID()); if(!stack || !stack->getOwner().isValidPlayer()) return; side = stack->unitSide(); } - auto & state = gs->players.at(gs->curB->getSidePlayer(side)); + auto player = gs->curB->getSidePlayer(side); + if(!player.isValidPlayer()) + return; - auto turnTimerUpdateApplier = [&](TurnTimerInfo & tTimer, int waitTime) + const auto * state = gameHandler.getPlayerState(player); + if(!state || state->status != EPlayerStatus::INGAME || !state->human) + return; + + auto & info = timerInfo[player]; + if(info.isEnabled && info.isBattle && !timerCountDown(info.timer.creatureTimer, si->turnTimerInfo.creatureTimer, player, waitTime)) { - if(tTimer.creatureTimer > 0) + if(isPvpBattle()) { - tTimer.creatureTimer -= waitTime; - timerLastUpdate[state.color] += waitTime; - int frequency = (tTimer.creatureTimer > turnTimePropagateThreshold - && si->turnTimerInfo.creatureTimer - tTimer.creatureTimer > turnTimePropagateThreshold) - ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit; - - if(state.status == EPlayerStatus::INGAME //do not send message if player is not active already - && timerLastUpdate[state.color] >= frequency) + if(info.timer.battleTimer > 0) { - TurnTimeUpdate ttu; - ttu.player = state.color; - ttu.turnTimer = tTimer; - gameHandler.sendAndApply(&ttu); - timerLastUpdate[state.color] = 0; + info.timer.creatureTimer = info.timer.battleTimer; + timerCountDown(info.timer.creatureTimer, info.timer.battleTimer, player, 0); + info.timer.battleTimer = 0; + } + else + { + BattleAction doNothing; + doNothing.side = side; + if(isTactisPhase) + doNothing.actionType = EActionType::END_TACTIC_PHASE; + else + { + doNothing.actionType = EActionType::DEFEND; + doNothing.stackNumber = stack->unitId(); + } + gameHandler.battles->makePlayerBattleAction(player, doNothing); } - return true; - } - return false; - }; - - if(state.human && timerEnabled[state.color] - && !turnTimerUpdateApplier(timers[state.color], waitTime)) - { - if(timers[state.color].battleTimer > 0) - { - timers[state.color].creatureTimer = timers[state.color].battleTimer; - timers[state.color].battleTimer = 0; - turnTimerUpdateApplier(timers[state.color], 0); } else { - BattleAction doNothing; - doNothing.side = side; - if(isTactisPhase) - doNothing.actionType = EActionType::END_TACTIC_PHASE; + if(info.timer.turnTimer > 0) + { + info.timer.creatureTimer = info.timer.turnTimer; + timerCountDown(info.timer.creatureTimer, info.timer.turnTimer, player, 0); + info.timer.turnTimer = 0; + } + else if(info.timer.baseTimer > 0) + { + info.timer.creatureTimer = info.timer.baseTimer; + timerCountDown(info.timer.creatureTimer, info.timer.baseTimer, player, 0); + info.timer.baseTimer = 0; + } else { - doNothing.actionType = EActionType::DEFEND; - doNothing.stackNumber = stack->unitId(); + BattleAction retreat; + retreat.side = side; + retreat.actionType = EActionType::RETREAT; //harsh punishment + gameHandler.battles->makePlayerBattleAction(player, retreat); } - gameHandler.battles->makePlayerBattleAction(state.color, doNothing); } } } diff --git a/server/TurnTimerHandler.h b/server/TurnTimerHandler.h index 572831da3..cdbcb6ca3 100644 --- a/server/TurnTimerHandler.h +++ b/server/TurnTimerHandler.h @@ -10,11 +10,12 @@ #pragma once +#include "../lib/TurnTimerInfo.h" + VCMI_LIB_NAMESPACE_BEGIN class CStack; class PlayerColor; -struct TurnTimerInfo; VCMI_LIB_NAMESPACE_END @@ -22,18 +23,28 @@ class CGameHandler; class TurnTimerHandler { + struct PlayerTimerInfo + { + TurnTimerInfo timer; + bool isEnabled = true; + bool isBattle = false; + int lastUpdate = 0; + }; + CGameHandler & gameHandler; const int turnTimePropagateFrequency = 5000; const int turnTimePropagateFrequencyCrit = 1000; const int turnTimePropagateThreshold = 3000; - std::map timers; - std::map timerEnabled; - std::map timerLastUpdate; + std::map timerInfo; std::recursive_mutex mx; void onPlayerMakingTurn(PlayerColor player, int waitTime); void onBattleLoop(int waitTime); + bool timerCountDown(int & timerToApply, int initialTimer, PlayerColor player, int waitTime); + bool isPvpBattle() const; + void sendTimerUpdate(PlayerColor player); + public: TurnTimerHandler(CGameHandler &); @@ -41,6 +52,7 @@ public: void onPlayerGetTurn(PlayerColor player); void onBattleStart(); void onBattleNextStack(const CStack & stack); + void onBattleEnd(); void update(int waitTime); void setTimerEnabled(PlayerColor player, bool enabled); }; diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index 6bf6e6fcb..f983b9797 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -264,6 +264,8 @@ void BattleResultProcessor::endBattle(int3 tile, const CGHeroInstance * heroAtta } gameHandler->sendAndApply(battleResult.get()); //after this point casualties objects are destroyed + + gameHandler->turnTimerHandler.onBattleEnd(); if (battleResult->queryID == QueryID::NONE) endBattleConfirm(gameHandler->gameState()->curB); @@ -463,7 +465,7 @@ void BattleResultProcessor::endBattleConfirm(const BattleInfo * battleInfo) raccepted.heroResult[1].hero = const_cast(battleInfo->sides.at(1).hero); raccepted.heroResult[0].exp = battleResult->exp[0]; raccepted.heroResult[1].exp = battleResult->exp[1]; - raccepted.winnerSide = finishingBattle->winnerSide; + raccepted.winnerSide = finishingBattle->winnerSide; gameHandler->sendAndApply(&raccepted); gameHandler->queries->popIfTop(battleQuery); From f30f00faa078cf43219f4b066cd468dc0ab51a60 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 30 Aug 2023 02:18:44 +0400 Subject: [PATCH 14/18] Timer pausing while waiting for player accept turn --- server/queries/MapQueries.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/queries/MapQueries.cpp b/server/queries/MapQueries.cpp index a237d9118..900c36cdd 100644 --- a/server/queries/MapQueries.cpp +++ b/server/queries/MapQueries.cpp @@ -28,12 +28,12 @@ bool PlayerStartsTurnQuery::blocksPack(const CPack *pack) const void PlayerStartsTurnQuery::onAdding(PlayerColor color) { - //gh->turnTimerHandler.setTimerEnabled(color, false); + gh->turnTimerHandler.setTimerEnabled(color, false); } void PlayerStartsTurnQuery::onRemoval(PlayerColor color) { - //gh->turnTimerHandler.setTimerEnabled(color, true); + gh->turnTimerHandler.setTimerEnabled(color, true); } bool PlayerStartsTurnQuery::endsByPlayerAnswer() const From 9a42abe2a7f8d46cb2346629f552be042432bdc8 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 30 Aug 2023 03:11:46 +0400 Subject: [PATCH 15/18] Extended timer info to exhange between client and server --- client/CPlayerInterface.cpp | 9 --- client/CPlayerInterface.h | 3 - client/adventureMap/TurnTimerWidget.cpp | 24 +++---- lib/TurnTimerInfo.h | 5 ++ server/TurnTimerHandler.cpp | 96 ++++++++++++------------- server/TurnTimerHandler.h | 13 +--- 6 files changed, 67 insertions(+), 83 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index fa035e9e2..79cef66f6 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -140,7 +140,6 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player): firstCall = 1; //if loading will be overwritten in serialize autosaveCount = 0; isAutoFightOn = false; - timerEnabled = true; duringMovement = false; ignoreEvents = false; numOfMovedArts = 0; @@ -272,8 +271,6 @@ void CPlayerInterface::yourTurn(QueryID queryID) makingTurn = true; adventureInt->onPlayerTurnStarted(playerID); } - - timerEnabled = false; } acceptTurn(queryID); } @@ -326,7 +323,6 @@ void CPlayerInterface::acceptTurn(QueryID queryID) } cb->selectionMade(0, queryID); - timerEnabled = true; } void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose) @@ -2127,8 +2123,3 @@ std::optional CPlayerInterface::makeSurrenderRetreatDecision(const { return std::nullopt; } - -bool CPlayerInterface::isTimerEnabled() const -{ - return timerEnabled; -} diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index 157158a71..c5048637f 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -57,7 +57,6 @@ namespace boost /// Central class for managing user interface logic class CPlayerInterface : public CGameInterface, public IUpdateable { - bool timerEnabled; bool duringMovement; bool ignoreEvents; size_t numOfMovedArts; @@ -207,8 +206,6 @@ public: // public interface for use by client via LOCPLINT access ///returns true if all events are processed internally bool capturedAllEvents(); - - bool isTimerEnabled() const; CPlayerInterface(PlayerColor Player); ~CPlayerInterface(); diff --git a/client/adventureMap/TurnTimerWidget.cpp b/client/adventureMap/TurnTimerWidget.cpp index 88b1ccd08..bd9606d62 100644 --- a/client/adventureMap/TurnTimerWidget.cpp +++ b/client/adventureMap/TurnTimerWidget.cpp @@ -102,22 +102,27 @@ void TurnTimerWidget::tick(uint32_t msPassed) if(!LOCPLINT || !LOCPLINT->cb) return; - for (PlayerColor p(0); p < PlayerColor::PLAYER_LIMIT; ++p) + for(PlayerColor p(0); p < PlayerColor::PLAYER_LIMIT; ++p) { auto player = p; if(LOCPLINT->battleInt) { if(auto * stack = LOCPLINT->battleInt->stacksController->getActiveStack()) player = stack->getOwner(); + else + continue; + if(p != player) + continue; } - - if(p != player || !LOCPLINT->cb->isPlayerMakingTurn(player)) + else if(!LOCPLINT->cb->isPlayerMakingTurn(player)) continue; auto time = LOCPLINT->cb->getPlayerTurnTime(player); - if(LOCPLINT->isTimerEnabled()) + if(time.isActive) cachedTurnTime -= msPassed; - if(cachedTurnTime < 0) cachedTurnTime = 0; //do not go below zero + + if(cachedTurnTime < 0) + cachedTurnTime = 0; //do not go below zero if(lastPlayer != player) { @@ -140,15 +145,10 @@ void TurnTimerWidget::tick(uint32_t msPassed) auto * playerInfo = LOCPLINT->cb->getPlayer(player); if(player.isValidPlayer() || (playerInfo && playerInfo->isHuman())) { - if(LOCPLINT->battleInt) - { - if(time.isBattleEnabled()) - timeCheckAndUpdate(time.creatureTimer); - } + if(time.isBattle) + timeCheckAndUpdate(time.creatureTimer); else - { timeCheckAndUpdate(time.turnTimer); - } } else timeCheckAndUpdate(0); diff --git a/lib/TurnTimerInfo.h b/lib/TurnTimerInfo.h index f29530726..843e0c0de 100644 --- a/lib/TurnTimerInfo.h +++ b/lib/TurnTimerInfo.h @@ -19,6 +19,9 @@ struct DLL_LINKAGE TurnTimerInfo int battleTimer = 0; //in ms, counts down during battles when creature timer runs out int creatureTimer = 0; //in ms, counts down when player is choosing action in battle + bool isActive = true; //should be paused if set to false + bool isBattle = false; //indicator for current timer mode + bool isEnabled() const; bool isBattleEnabled() const; @@ -29,6 +32,8 @@ struct DLL_LINKAGE TurnTimerInfo h & baseTimer; h & battleTimer; h & creatureTimer; + h & isActive; + h & isBattle; } }; diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index 936b2926b..7473b2f8c 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -31,11 +31,11 @@ void TurnTimerHandler::onGameplayStart(PlayerColor player) std::lock_guard guard(mx); if(const auto * si = gameHandler.getStartInfo()) { - timerInfo[player].timer = si->turnTimerInfo; - timerInfo[player].timer.turnTimer = 0; - timerInfo[player].isEnabled = true; - timerInfo[player].isBattle = false; - timerInfo[player].lastUpdate = std::numeric_limits::max(); + timers[player] = si->turnTimerInfo; + timers[player].turnTimer = 0; + timers[player].isActive = true; + timers[player].isBattle = false; + lastUpdate[player] = std::numeric_limits::max(); } } @@ -43,17 +43,16 @@ void TurnTimerHandler::setTimerEnabled(PlayerColor player, bool enabled) { std::lock_guard guard(mx); assert(player.isValidPlayer()); - timerInfo[player].isEnabled = enabled; + timers[player].isActive = enabled; } void TurnTimerHandler::sendTimerUpdate(PlayerColor player) { - auto & info = timerInfo[player]; TurnTimeUpdate ttu; ttu.player = player; - ttu.turnTimer = info.timer; + ttu.turnTimer = timers[player]; gameHandler.sendAndApply(&ttu); - info.lastUpdate = 0; + lastUpdate[player] = 0; } void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) @@ -63,10 +62,10 @@ void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) { if(si->turnTimerInfo.isEnabled()) { - auto & info = timerInfo[player]; + auto & timer = timers[player]; if(si->turnTimerInfo.baseTimer > 0) - info.timer.baseTimer += info.timer.turnTimer; - info.timer.turnTimer = si->turnTimerInfo.turnTimer; + timer.baseTimer += timer.turnTimer; + timer.turnTimer = si->turnTimerInfo.turnTimer; sendTimerUpdate(player); } @@ -91,14 +90,13 @@ bool TurnTimerHandler::timerCountDown(int & timer, int initialTimer, PlayerColor { if(timer > 0) { - auto & info = timerInfo[player]; timer -= waitTime; - info.lastUpdate += waitTime; + lastUpdate[player] += waitTime; int frequency = (timer > turnTimePropagateThreshold && initialTimer - timer > turnTimePropagateThreshold) ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit; - if(info.lastUpdate >= frequency) + if(lastUpdate[player] >= frequency) sendTimerUpdate(player); return true; @@ -114,16 +112,16 @@ void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime) if(!si || !gs || !si->turnTimerInfo.isEnabled()) return; - auto & info = timerInfo[player]; + auto & timer = timers[player]; const auto * state = gameHandler.getPlayerState(player); - if(state && state->human && info.isEnabled && !info.isBattle && state->status == EPlayerStatus::INGAME) + if(state && state->human && timer.isActive && !timer.isBattle && state->status == EPlayerStatus::INGAME) { - if(!timerCountDown(info.timer.turnTimer, si->turnTimerInfo.turnTimer, player, waitTime)) + if(!timerCountDown(timer.turnTimer, si->turnTimerInfo.turnTimer, player, waitTime)) { - if(info.timer.baseTimer > 0) + if(timer.baseTimer > 0) { - info.timer.turnTimer = info.timer.baseTimer; - info.timer.baseTimer = 0; + timer.turnTimer = timer.baseTimer; + timer.baseTimer = 0; onPlayerMakingTurn(player, 0); } else if(!gameHandler.queries->topQuery(state->color)) //wait for replies to avoid pending queries @@ -164,10 +162,10 @@ void TurnTimerHandler::onBattleStart() { if(i.isValidPlayer()) { - auto & info = timerInfo[i]; - info.isBattle = true; - info.timer.battleTimer = (pvpBattle ? si->turnTimerInfo.battleTimer : 0); - info.timer.creatureTimer = (pvpBattle ? si->turnTimerInfo.creatureTimer : si->turnTimerInfo.battleTimer); + auto & timer = timers[i]; + timer.isBattle = true; + timer.battleTimer = (pvpBattle ? si->turnTimerInfo.battleTimer : 0); + timer.creatureTimer = (pvpBattle ? si->turnTimerInfo.creatureTimer : si->turnTimerInfo.battleTimer); sendTimerUpdate(i); } @@ -191,12 +189,12 @@ void TurnTimerHandler::onBattleEnd() { if(i.isValidPlayer() && !pvpBattle) { - auto & info = timerInfo[i]; - info.isBattle = false; - if(si->turnTimerInfo.baseTimer && info.timer.baseTimer == 0) - info.timer.baseTimer = info.timer.creatureTimer; - else if(si->turnTimerInfo.turnTimer && info.timer.turnTimer == 0) - info.timer.turnTimer = info.timer.creatureTimer; + auto & timer = timers[i]; + timer.isBattle = false; + if(si->turnTimerInfo.baseTimer && timer.baseTimer == 0) + timer.baseTimer = timer.creatureTimer; + else if(si->turnTimerInfo.turnTimer && timer.turnTimer == 0) + timer.turnTimer = timer.creatureTimer; sendTimerUpdate(i); } @@ -215,10 +213,10 @@ void TurnTimerHandler::onBattleNextStack(const CStack & stack) { auto player = stack.getOwner(); - auto & info = timerInfo[player]; - if(info.timer.battleTimer == 0) - info.timer.battleTimer = info.timer.creatureTimer; - info.timer.creatureTimer = si->turnTimerInfo.creatureTimer; + auto & timer = timers[player]; + if(timer.battleTimer == 0) + timer.battleTimer = timer.creatureTimer; + timer.creatureTimer = si->turnTimerInfo.creatureTimer; sendTimerUpdate(player); } @@ -254,16 +252,16 @@ void TurnTimerHandler::onBattleLoop(int waitTime) if(!state || state->status != EPlayerStatus::INGAME || !state->human) return; - auto & info = timerInfo[player]; - if(info.isEnabled && info.isBattle && !timerCountDown(info.timer.creatureTimer, si->turnTimerInfo.creatureTimer, player, waitTime)) + auto & timer = timers[player]; + if(timer.isActive && timer.isBattle && !timerCountDown(timer.creatureTimer, si->turnTimerInfo.creatureTimer, player, waitTime)) { if(isPvpBattle()) { - if(info.timer.battleTimer > 0) + if(timer.battleTimer > 0) { - info.timer.creatureTimer = info.timer.battleTimer; - timerCountDown(info.timer.creatureTimer, info.timer.battleTimer, player, 0); - info.timer.battleTimer = 0; + timer.creatureTimer = timer.battleTimer; + timerCountDown(timer.creatureTimer, timer.battleTimer, player, 0); + timer.battleTimer = 0; } else { @@ -281,17 +279,17 @@ void TurnTimerHandler::onBattleLoop(int waitTime) } else { - if(info.timer.turnTimer > 0) + if(timer.turnTimer > 0) { - info.timer.creatureTimer = info.timer.turnTimer; - timerCountDown(info.timer.creatureTimer, info.timer.turnTimer, player, 0); - info.timer.turnTimer = 0; + timer.creatureTimer = timer.turnTimer; + timerCountDown(timer.creatureTimer, timer.turnTimer, player, 0); + timer.turnTimer = 0; } - else if(info.timer.baseTimer > 0) + else if(timer.baseTimer > 0) { - info.timer.creatureTimer = info.timer.baseTimer; - timerCountDown(info.timer.creatureTimer, info.timer.baseTimer, player, 0); - info.timer.baseTimer = 0; + timer.creatureTimer = timer.baseTimer; + timerCountDown(timer.creatureTimer, timer.baseTimer, player, 0); + timer.baseTimer = 0; } else { diff --git a/server/TurnTimerHandler.h b/server/TurnTimerHandler.h index cdbcb6ca3..5039dc07b 100644 --- a/server/TurnTimerHandler.h +++ b/server/TurnTimerHandler.h @@ -22,20 +22,13 @@ VCMI_LIB_NAMESPACE_END class CGameHandler; class TurnTimerHandler -{ - struct PlayerTimerInfo - { - TurnTimerInfo timer; - bool isEnabled = true; - bool isBattle = false; - int lastUpdate = 0; - }; - +{ CGameHandler & gameHandler; const int turnTimePropagateFrequency = 5000; const int turnTimePropagateFrequencyCrit = 1000; const int turnTimePropagateThreshold = 3000; - std::map timerInfo; + std::map timers; + std::map lastUpdate; std::recursive_mutex mx; void onPlayerMakingTurn(PlayerColor player, int waitTime); From cc3ca9a41e4af9a8ab78e4056932c5500f5898b0 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 30 Aug 2023 03:33:59 +0400 Subject: [PATCH 16/18] Timers fixes --- client/adventureMap/TurnTimerWidget.cpp | 99 ++++++++++++------------ client/adventureMap/TurnTimerWidget.h | 2 + lib/TurnTimerInfo.h | 2 +- server/TurnTimerHandler.cpp | 14 ++-- server/battles/BattleResultProcessor.cpp | 3 +- 5 files changed, 64 insertions(+), 56 deletions(-) diff --git a/client/adventureMap/TurnTimerWidget.cpp b/client/adventureMap/TurnTimerWidget.cpp index bd9606d62..b47d111da 100644 --- a/client/adventureMap/TurnTimerWidget.cpp +++ b/client/adventureMap/TurnTimerWidget.cpp @@ -97,60 +97,63 @@ void TurnTimerWidget::setTime(PlayerColor player, int time) } } +void TurnTimerWidget::updateTimer(PlayerColor player, uint32_t msPassed) +{ + const auto & time = LOCPLINT->cb->getPlayerTurnTime(player); + if(time.isActive) + cachedTurnTime -= msPassed; + + if(cachedTurnTime < 0) + cachedTurnTime = 0; //do not go below zero + + if(lastPlayer != player) + { + lastPlayer = player; + lastTurnTime = 0; + } + + auto timeCheckAndUpdate = [&](int time) + { + if(time / 1000 != lastTurnTime / 1000) + { + //do not update timer on this tick + lastTurnTime = time; + cachedTurnTime = time; + } + else + setTime(player, cachedTurnTime); + }; + + auto * playerInfo = LOCPLINT->cb->getPlayer(player); + if(player.isValidPlayer() || (playerInfo && playerInfo->isHuman())) + { + if(time.isBattle) + timeCheckAndUpdate(time.creatureTimer); + else + timeCheckAndUpdate(time.turnTimer); + } + else + timeCheckAndUpdate(0); +} + void TurnTimerWidget::tick(uint32_t msPassed) { if(!LOCPLINT || !LOCPLINT->cb) return; - for(PlayerColor p(0); p < PlayerColor::PLAYER_LIMIT; ++p) + if(LOCPLINT->battleInt) { - auto player = p; - if(LOCPLINT->battleInt) - { - if(auto * stack = LOCPLINT->battleInt->stacksController->getActiveStack()) - player = stack->getOwner(); - else - continue; - if(p != player) - continue; - } - else if(!LOCPLINT->cb->isPlayerMakingTurn(player)) - continue; - - auto time = LOCPLINT->cb->getPlayerTurnTime(player); - if(time.isActive) - cachedTurnTime -= msPassed; - - if(cachedTurnTime < 0) - cachedTurnTime = 0; //do not go below zero - - if(lastPlayer != player) - { - lastPlayer = player; - lastTurnTime = 0; - } - - auto timeCheckAndUpdate = [&](int time) - { - if(time / 1000 != lastTurnTime / 1000) - { - //do not update timer on this tick - lastTurnTime = time; - cachedTurnTime = time; - } - else - setTime(player, cachedTurnTime); - }; - - auto * playerInfo = LOCPLINT->cb->getPlayer(player); - if(player.isValidPlayer() || (playerInfo && playerInfo->isHuman())) - { - if(time.isBattle) - timeCheckAndUpdate(time.creatureTimer); - else - timeCheckAndUpdate(time.turnTimer); - } + if(auto * stack = LOCPLINT->battleInt->stacksController->getActiveStack()) + updateTimer(stack->getOwner(), msPassed); else - timeCheckAndUpdate(0); + updateTimer(PlayerColor::NEUTRAL, msPassed); + } + else + { + for(PlayerColor p(0); p < PlayerColor::PLAYER_LIMIT; ++p) + { + if(LOCPLINT->cb->isPlayerMakingTurn(p)) + updateTimer(p, msPassed); + } } } diff --git a/client/adventureMap/TurnTimerWidget.h b/client/adventureMap/TurnTimerWidget.h index ccc801eb4..f8b4b97fc 100644 --- a/client/adventureMap/TurnTimerWidget.h +++ b/client/adventureMap/TurnTimerWidget.h @@ -46,6 +46,8 @@ private: std::set notifications; std::shared_ptr buildDrawRect(const JsonNode & config) const; + + void updateTimer(PlayerColor player, uint32_t msPassed); public: diff --git a/lib/TurnTimerInfo.h b/lib/TurnTimerInfo.h index 843e0c0de..c708345b4 100644 --- a/lib/TurnTimerInfo.h +++ b/lib/TurnTimerInfo.h @@ -19,7 +19,7 @@ struct DLL_LINKAGE TurnTimerInfo int battleTimer = 0; //in ms, counts down during battles when creature timer runs out int creatureTimer = 0; //in ms, counts down when player is choosing action in battle - bool isActive = true; //should be paused if set to false + bool isActive = false; //is being counting down bool isBattle = false; //indicator for current timer mode bool isEnabled() const; diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index 7473b2f8c..9dae655af 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -187,14 +187,18 @@ void TurnTimerHandler::onBattleEnd() for(auto i : {attacker, defender}) { - if(i.isValidPlayer() && !pvpBattle) + if(i.isValidPlayer()) { auto & timer = timers[i]; timer.isBattle = false; - if(si->turnTimerInfo.baseTimer && timer.baseTimer == 0) - timer.baseTimer = timer.creatureTimer; - else if(si->turnTimerInfo.turnTimer && timer.turnTimer == 0) - timer.turnTimer = timer.creatureTimer; + + if(!pvpBattle) + { + if(si->turnTimerInfo.baseTimer && timer.baseTimer == 0) + timer.baseTimer = timer.creatureTimer; + else if(si->turnTimerInfo.turnTimer && timer.turnTimer == 0) + timer.turnTimer = timer.creatureTimer; + } sendTimerUpdate(i); } diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index f983b9797..bf0827949 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -263,9 +263,8 @@ void BattleResultProcessor::endBattle(int3 tile, const CGHeroInstance * heroAtta otherBattleQuery->result = battleQuery->result; } - gameHandler->sendAndApply(battleResult.get()); //after this point casualties objects are destroyed - gameHandler->turnTimerHandler.onBattleEnd(); + gameHandler->sendAndApply(battleResult.get()); //after this point casualties objects are destroyed if (battleResult->queryID == QueryID::NONE) endBattleConfirm(gameHandler->gameState()->curB); From 00216168bf2aacb00591d83ad1ee522b9a0c156e Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 30 Aug 2023 03:44:09 +0400 Subject: [PATCH 17/18] Timer works as designed --- server/CGameHandler.cpp | 7 +++---- server/TurnTimerHandler.cpp | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index cbe6505b5..2ca17f61d 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1178,11 +1178,10 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo for(auto topQuery = queries->topQuery(h->tempOwner); true; topQuery = queries->topQuery(h->tempOwner)) { moveQuery = std::dynamic_pointer_cast(topQuery); - if(moveQuery - && (!transit || result != TryMoveHero::SUCCESS)) - queries->popIfTop(moveQuery); - else + if(!moveQuery || (transit && result == TryMoveHero::SUCCESS)) break; + + queries->popIfTop(moveQuery); } logGlobal->trace("Hero %s ends movement", h->getNameTranslated()); return result != TryMoveHero::FAILED; diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index 9dae655af..f7e7a2688 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -44,6 +44,7 @@ void TurnTimerHandler::setTimerEnabled(PlayerColor player, bool enabled) std::lock_guard guard(mx); assert(player.isValidPlayer()); timers[player].isActive = enabled; + sendTimerUpdate(player); } void TurnTimerHandler::sendTimerUpdate(PlayerColor player) From d7f06986512d53388458999b360d813b4b718ce7 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 31 Aug 2023 20:46:48 +0400 Subject: [PATCH 18/18] Change text hints --- Mods/vcmi/config/vcmi/english.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index cb7c2489f..4fb4c6722 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -191,10 +191,10 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Team Alignments", "vcmi.randomMapTab.widgets.roadTypesLabel" : "Road Types", - "vcmi.optionsTab.widgets.chessFieldBase.help" : "{Base timer}\n\nCounts down when {turn timer} runs out, never resets. If timer expires, player ends turn.", - "vcmi.optionsTab.widgets.chessFieldTurn.help" : "{Turn timer}\n\nCounts down when player is making turn on adventure map, resets every turn. If {base timer} is enabled, unspent time will be added to it.", - "vcmi.optionsTab.widgets.chessFieldBattle.help" : "{Battle timer}\n\nCounts down during battles when {stack timer} runs out, resets every battle. if timer expires, currently acting stack will defend.", - "vcmi.optionsTab.widgets.chessFieldCreature.help" : "{Creature timer}\n\nCounts down when player is choosing action in battle, resets after each stack turn.", + "vcmi.optionsTab.widgets.chessFieldBase.help" : "{Extra timer}\n\nStarts counting down when the {turn timer} reaches zero. It is set only once at the beginning of the game. When this timer reaches zero, the player's turn ends.", + "vcmi.optionsTab.widgets.chessFieldTurn.help" : "{Turn timer}\n\nStarts counting down when the player starts their turn on the adventure map. It is reset to its initial value at the start of each turn. Any unused turn time will be added to the {Extra timer} if it is in use.", + "vcmi.optionsTab.widgets.chessFieldBattle.help" : "{Battle timer}\n\nCounts down during battles when the {stack timer} reaches zero. It is reset to its initial value at the start of each battle. If the timer reaches zero, the currently active stack will defend.", + "vcmi.optionsTab.widgets.chessFieldCreature.help" : "{Stack timer}\n\nStarts counting down when the player is selecting an action for the current stack during battle. It resets to its initial value after the stack's action is completed.", "vcmi.optionsTab.widgets.labelTimer" : "Timer", "vcmi.optionsTab.widgets.timerModeSwitch.classic" : "Classic timer", "vcmi.optionsTab.widgets.timerModeSwitch.chess" : "Chess timer",