Explorar o código

Merge pull request #2698 from Nordsoft91/turn-timer

Turn timer fixes
Nordsoft91 %!s(int64=2) %!d(string=hai) anos
pai
achega
c45ab07d0b

+ 9 - 0
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!",

+ 0 - 1
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;

+ 1 - 1
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<TurnTimerWidget>();
 	
 	addUsedEvents(KEYBOARD | TIME);

+ 50 - 45
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(auto * stack = LOCPLINT->battleInt->stacksController->getActiveStack())
+			updateTimer(stack->getOwner(), msPassed);
+		else
+			updateTimer(PlayerColor::NEUTRAL, msPassed);
+	}
+	else
+	{
+		for(PlayerColor p(0); p < PlayerColor::PLAYER_LIMIT; ++p)
 		{
-			if(LOCPLINT->battleInt)
-			{
-				if(time.isBattleEnabled())
-					timeCheckAndUpdate(time.creatureTimer);
-			}
-			else
-			{
-				timeCheckAndUpdate(time.turnTimer);
-			}
+			if(LOCPLINT->cb->isPlayerMakingTurn(p))
+				updateTimer(p, msPassed);
 		}
-		else
-			timeCheckAndUpdate(0);
 	}
 }

+ 2 - 0
client/adventureMap/TurnTimerWidget.h

@@ -46,6 +46,8 @@ private:
 	std::set<int> notifications;
 	
 	std::shared_ptr<DrawRect> buildDrawRect(const JsonNode & config) const;
+	
+	void updateTimer(PlayerColor player, uint32_t msPassed);
 
 public:
 

+ 2 - 2
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()

+ 4 - 2
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<CTextInput> 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())

+ 2 - 2
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}
 		},
 	],
 

+ 2 - 2
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

+ 5 - 0
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;
 	}
 };
 

+ 9 - 14
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<std::chrono::milliseconds>(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<CHeroMovementQuery>(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;

+ 195 - 83
server/TurnTimerHandler.cpp

@@ -28,168 +28,280 @@ TurnTimerHandler::TurnTimerHandler(CGameHandler & gh):
 
 void TurnTimerHandler::onGameplayStart(PlayerColor player)
 {
+	std::lock_guard<std::recursive_mutex> 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<int>::max();
 	}
 }
 
+void TurnTimerHandler::setTimerEnabled(PlayerColor player, bool enabled)
+{
+	std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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::onBattleNextStack(const CStack & stack)
+void TurnTimerHandler::onBattleEnd()
 {
+	std::lock_guard<std::recursive_mutex> 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);
 	
-	auto player = stack.getOwner();
+	bool pvpBattle = isPvpBattle();
 	
-	if(!player.isValidPlayer())
+	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<std::recursive_mutex> guard(mx);
+	const auto * gs = gameHandler.gameState();
+	const auto * si = gameHandler.getStartInfo();
+	if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled())
 		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<std::recursive_mutex> 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);
+				timer.creatureTimer = timer.battleTimer;
+				timerCountDown(timer.creatureTimer, timer.battleTimer, player, 0);
+				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 && si->turnTimerInfo.isBattleEnabled())
-	{
-		if(!turnTimerUpdateApplier(timers[state.color], waitTime))
+		else
 		{
-			if(timers[state.color].battleTimer > 0)
+			if(timer.turnTimer > 0)
 			{
-				timers[state.color].creatureTimer = timers[state.color].battleTimer;
-				timers[state.color].battleTimer = 0;
-				turnTimerUpdateApplier(timers[state.color], 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 doNothing;
-				doNothing.actionType = EActionType::DEFEND;
-				doNothing.side = stack->unitSide();
-				doNothing.stackNumber = stack->unitId();
-				gameHandler.battles->makePlayerBattleAction(state.color, doNothing);
+				BattleAction retreat;
+				retreat.side = side;
+				retreat.actionType = EActionType::RETREAT; //harsh punishment
+				gameHandler.battles->makePlayerBattleAction(player, retreat);
 			}
 		}
 	}

+ 15 - 4
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<PlayerColor, TurnTimerInfo> timers;
+	std::map<PlayerColor, int> 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);
 };

+ 2 - 1
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<CGHeroInstance*>(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);

+ 2 - 2
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