diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index f60af4153..77c52cd66 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -193,6 +193,15 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Team Alignments", "vcmi.randomMapTab.widgets.roadTypesLabel" : "Road Types", + "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", + + // 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/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index ca45b0a93..2240cd638 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; - duringMovement = false; ignoreEvents = false; numOfMovedArts = 0; 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/client/adventureMap/TurnTimerWidget.cpp b/client/adventureMap/TurnTimerWidget.cpp index 68428ad03..b47d111da 100644 --- a/client/adventureMap/TurnTimerWidget.cpp +++ b/client/adventureMap/TurnTimerWidget.cpp @@ -97,58 +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 if (!LOCPLINT->cb->isPlayerMakingTurn(player)) - continue; - - auto time = LOCPLINT->cb->getPlayerTurnTime(player); - 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(LOCPLINT->battleInt) - { - if(time.isBattleEnabled()) - 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/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/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index ccd452b5f..dc97aba1a 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -158,6 +158,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); } @@ -579,8 +581,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()) 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} }, ], 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/lib/TurnTimerInfo.h b/lib/TurnTimerInfo.h index f29530726..c708345b4 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 = false; //is being counting down + 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/CGameHandler.cpp b/server/CGameHandler.cpp index 4e8d1b62e..2ca17f61d 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -999,18 +999,14 @@ void CGameHandler::run(bool resume) turnOrder->onGameStarted(); //wait till game is done + auto clockLast = std::chrono::steady_clock::now(); while(lobby->getState() == EServerState::GAMEPLAY) { - const int waitTime = 100; //ms - - for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) - if(gs->isPlayerMakingTurn(player)) - turnTimerHandler.onPlayerMakingTurn(player, waitTime); - - if(gs->curB) - turnTimerHandler.onBattleLoop(waitTime); - - boost::this_thread::sleep_for(boost::chrono::milliseconds(waitTime)); + const auto clockDuration = std::chrono::steady_clock::now() - clockLast; + const int timePassed = std::chrono::duration_cast(clockDuration).count(); + clockLast += clockDuration; + turnTimerHandler.update(timePassed); + boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); } } @@ -1182,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 d3aca09d9..f7e7a2688 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -28,168 +28,280 @@ TurnTimerHandler::TurnTimerHandler(CGameHandler & gh): void TurnTimerHandler::onGameplayStart(PlayerColor player) { + std::lock_guard guard(mx); if(const auto * si = gameHandler.getStartInfo()) { - if(si->turnTimerInfo.isEnabled()) - { - timers[player] = si->turnTimerInfo; - timers[player].turnTimer = 0; - } + timers[player] = si->turnTimerInfo; + timers[player].turnTimer = 0; + timers[player].isActive = true; + timers[player].isBattle = false; + lastUpdate[player] = std::numeric_limits::max(); } } +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) +{ + TurnTimeUpdate ttu; + ttu.player = player; + ttu.turnTimer = timers[player]; + gameHandler.sendAndApply(&ttu); + lastUpdate[player] = 0; +} + void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) { + std::lock_guard guard(mx); if(const auto * si = gameHandler.getStartInfo()) { if(si->turnTimerInfo.isEnabled()) { - timers[player].baseTimer += timers[player].turnTimer; - timers[player].turnTimer = si->turnTimerInfo.turnTimer; + auto & timer = timers[player]; + if(si->turnTimerInfo.baseTimer > 0) + timer.baseTimer += timer.turnTimer; + timer.turnTimer = si->turnTimerInfo.turnTimer; - TurnTimeUpdate ttu; - ttu.player = player; - ttu.turnTimer = timers[player]; - gameHandler.sendAndApply(&ttu); + sendTimerUpdate(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); + } +} + +bool TurnTimerHandler::timerCountDown(int & timer, int initialTimer, PlayerColor player, int waitTime) +{ + if(timer > 0) + { + timer -= waitTime; + lastUpdate[player] += waitTime; + int frequency = (timer > turnTimePropagateThreshold + && initialTimer - timer > turnTimePropagateThreshold) + ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit; + + if(lastUpdate[player] >= 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) + if(!si || !gs || !si->turnTimerInfo.isEnabled()) return; - auto & state = gs->players.at(player); - - if(state.human && si->turnTimerInfo.isEnabled() && !gs->curB) + auto & timer = timers[player]; + const auto * state = gameHandler.getPlayerState(player); + if(state && state->human && timer.isActive && !timer.isBattle && state->status == EPlayerStatus::INGAME) { - if(timers[player].turnTimer > 0) + if(!timerCountDown(timer.turnTimer, si->turnTimerInfo.turnTimer, player, waitTime)) { - timers[player].turnTimer -= 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) + if(timer.baseTimer > 0) { - TurnTimeUpdate ttu; - ttu.player = state.color; - ttu.turnTimer = timers[player]; - gameHandler.sendAndApply(&ttu); + timer.turnTimer = timer.baseTimer; + 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); 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()) { - timers[i].battleTimer = si->turnTimerInfo.battleTimer; - timers[i].creatureTimer = si->turnTimerInfo.creatureTimer; + auto & timer = timers[i]; + timer.isBattle = true; + timer.battleTimer = (pvpBattle ? si->turnTimerInfo.battleTimer : 0); + timer.creatureTimer = (pvpBattle ? si->turnTimerInfo.creatureTimer : si->turnTimerInfo.battleTimer); - TurnTimeUpdate ttu; - ttu.player = i; - ttu.turnTimer = timers[i]; - gameHandler.sendAndApply(&ttu); + 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()) + { + auto & timer = timers[i]; + timer.isBattle = false; + + 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); } } } 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; - auto player = stack.getOwner(); - - if(!player.isValidPlayer()) - return; + if(isPvpBattle()) + { + auto player = stack.getOwner(); - if(timers[player].battleTimer < si->turnTimerInfo.battleTimer) - timers[player].battleTimer = timers[player].creatureTimer; - timers[player].creatureTimer = si->turnTimerInfo.creatureTimer; + auto & timer = timers[player]; + if(timer.battleTimer == 0) + timer.battleTimer = timer.creatureTimer; + timer.creatureTimer = si->turnTimerInfo.creatureTimer; - TurnTimeUpdate ttu; - ttu.player = player; - ttu.turnTimer = timers[player]; - gameHandler.sendAndApply(&ttu); + sendTimerUpdate(player); + } } 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) + if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled()) return; - const auto * stack = gs->curB.get()->battleGetStackByID(gs->curB->getActiveStackID()); - if(!stack || !stack->getOwner().isValidPlayer()) + ui8 side = 0; + const CStack * stack = nullptr; + bool isTactisPhase = gs->curB->battleTacticDist() > 0; + + if(isTactisPhase) + side = gs->curB->battleGetTacticsSide(); + else + { + stack = gs->curB->battleGetStackByID(gs->curB->getActiveStackID()); + if(!stack || !stack->getOwner().isValidPlayer()) + return; + side = stack->unitSide(); + } + + auto player = gs->curB->getSidePlayer(side); + if(!player.isValidPlayer()) return; - auto & state = gs->players.at(gs->curB->getSidePlayer(stack->unitSide())); + const auto * state = gameHandler.getPlayerState(player); + if(!state || state->status != EPlayerStatus::INGAME || !state->human) + return; - auto turnTimerUpdateApplier = [&](TurnTimerInfo & tTimer, int waitTime) + auto & timer = timers[player]; + if(timer.isActive && timer.isBattle && !timerCountDown(timer.creatureTimer, si->turnTimerInfo.creatureTimer, player, waitTime)) { - if(tTimer.creatureTimer > 0) + if(isPvpBattle()) { - tTimer.creatureTimer -= 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 % frequency == 0) + if(timer.battleTimer > 0) { - TurnTimeUpdate ttu; - ttu.player = state.color; - ttu.turnTimer = tTimer; - gameHandler.sendAndApply(&ttu); - } - return true; - } - return false; - }; - - if(state.human && si->turnTimerInfo.isBattleEnabled()) - { - if(!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); + timer.creatureTimer = timer.battleTimer; + timerCountDown(timer.creatureTimer, timer.battleTimer, player, 0); + timer.battleTimer = 0; } else { BattleAction doNothing; - doNothing.actionType = EActionType::DEFEND; - doNothing.side = stack->unitSide(); - doNothing.stackNumber = stack->unitId(); - gameHandler.battles->makePlayerBattleAction(state.color, 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); + } + } + else + { + if(timer.turnTimer > 0) + { + timer.creatureTimer = timer.turnTimer; + timerCountDown(timer.creatureTimer, timer.turnTimer, player, 0); + timer.turnTimer = 0; + } + else if(timer.baseTimer > 0) + { + timer.creatureTimer = timer.baseTimer; + timerCountDown(timer.creatureTimer, timer.baseTimer, player, 0); + timer.baseTimer = 0; + } + else + { + BattleAction retreat; + retreat.side = side; + retreat.actionType = EActionType::RETREAT; //harsh punishment + gameHandler.battles->makePlayerBattleAction(player, retreat); } } } diff --git a/server/TurnTimerHandler.h b/server/TurnTimerHandler.h index d805905a4..5039dc07b 100644 --- a/server/TurnTimerHandler.h +++ b/server/TurnTimerHandler.h @@ -10,31 +10,42 @@ #pragma once +#include "../lib/TurnTimerInfo.h" + VCMI_LIB_NAMESPACE_BEGIN class CStack; class PlayerColor; -struct TurnTimerInfo; VCMI_LIB_NAMESPACE_END class CGameHandler; class TurnTimerHandler -{ +{ CGameHandler & gameHandler; const int turnTimePropagateFrequency = 5000; const int turnTimePropagateFrequencyCrit = 1000; const int turnTimePropagateThreshold = 3000; std::map timers; + std::map lastUpdate; + 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 &); 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 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..bf0827949 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -263,6 +263,7 @@ void BattleResultProcessor::endBattle(int3 tile, const CGHeroInstance * heroAtta otherBattleQuery->result = battleQuery->result; } + gameHandler->turnTimerHandler.onBattleEnd(); gameHandler->sendAndApply(battleResult.get()); //after this point casualties objects are destroyed if (battleResult->queryID == QueryID::NONE) @@ -463,7 +464,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); 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