1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-20 20:23:03 +02:00

Merge pull request #2698 from Nordsoft91/turn-timer

Turn timer fixes
This commit is contained in:
Nordsoft91 2023-09-01 00:31:52 +04:00 committed by GitHub
commit c45ab07d0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 305 additions and 164 deletions

View File

@ -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!",

View File

@ -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;

View File

@ -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);

View File

@ -97,25 +97,14 @@ void TurnTimerWidget::setTime(PlayerColor player, int time)
}
}
void TurnTimerWidget::tick(uint32_t msPassed)
void TurnTimerWidget::updateTimer(PlayerColor player, uint32_t msPassed)
{
if(!LOCPLINT || !LOCPLINT->cb)
return;
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 if (!LOCPLINT->cb->isPlayerMakingTurn(player))
continue;
auto time = LOCPLINT->cb->getPlayerTurnTime(player);
const auto & time = LOCPLINT->cb->getPlayerTurnTime(player);
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)
{
@ -138,17 +127,33 @@ void TurnTimerWidget::tick(uint32_t msPassed)
auto * playerInfo = LOCPLINT->cb->getPlayer(player);
if(player.isValidPlayer() || (playerInfo && playerInfo->isHuman()))
{
if(LOCPLINT->battleInt)
{
if(time.isBattleEnabled())
if(time.isBattle)
timeCheckAndUpdate(time.creatureTimer);
}
else
{
timeCheckAndUpdate(time.turnTimer);
}
}
else
timeCheckAndUpdate(0);
}
void TurnTimerWidget::tick(uint32_t msPassed)
{
if(!LOCPLINT || !LOCPLINT->cb)
return;
if(LOCPLINT->battleInt)
{
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->cb->isPlayerMakingTurn(p))
updateTimer(p, msPassed);
}
}
}

View File

@ -47,6 +47,8 @@ private:
std::shared_ptr<DrawRect> buildDrawRect(const JsonNode & config) const;
void updateTimer(PlayerColor player, uint32_t msPassed);
public:
void show(Canvas & to) override;

View File

@ -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()

View File

@ -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())

View File

@ -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}
},
],

View File

@ -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

View File

@ -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;
}
};

View File

@ -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;

View File

@ -28,71 +28,127 @@ 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].isActive = true;
timers[player].isBattle = false;
lastUpdate[player] = std::numeric_limits<int>::max();
}
}
void TurnTimerHandler::onPlayerGetTurn(PlayerColor player)
void TurnTimerHandler::setTimerEnabled(PlayerColor player, bool enabled)
{
if(const auto * si = gameHandler.getStartInfo())
{
if(si->turnTimerInfo.isEnabled())
{
timers[player].baseTimer += timers[player].turnTimer;
timers[player].turnTimer = si->turnTimerInfo.turnTimer;
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())
{
auto & timer = timers[player];
if(si->turnTimerInfo.baseTimer > 0)
timer.baseTimer += timer.turnTimer;
timer.turnTimer = si->turnTimerInfo.turnTimer;
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);
}
}
else if(timers[player].baseTimer > 0)
{
timers[player].turnTimer = timers[player].baseTimer;
timers[player].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
gameHandler.turnOrder->onPlayerEndsTurn(state.color);
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())
@ -101,95 +157,151 @@ 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 & 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<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())
{
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(!player.isValidPlayer())
return;
auto & timer = timers[player];
if(timer.battleTimer == 0)
timer.battleTimer = timer.creatureTimer;
timer.creatureTimer = si->turnTimerInfo.creatureTimer;
if(timers[player].battleTimer < si->turnTimerInfo.battleTimer)
timers[player].battleTimer = timers[player].creatureTimer;
timers[player].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());
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;
auto & state = gs->players.at(gs->curB->getSidePlayer(stack->unitSide()));
auto turnTimerUpdateApplier = [&](TurnTimerInfo & tTimer, int waitTime)
{
if(tTimer.creatureTimer > 0)
{
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)
{
TurnTimeUpdate ttu;
ttu.player = state.color;
ttu.turnTimer = tTimer;
gameHandler.sendAndApply(&ttu);
side = stack->unitSide();
}
return true;
}
return false;
};
if(state.human && si->turnTimerInfo.isBattleEnabled())
auto player = gs->curB->getSidePlayer(side);
if(!player.isValidPlayer())
return;
const auto * state = gameHandler.getPlayerState(player);
if(!state || state->status != EPlayerStatus::INGAME || !state->human)
return;
auto & timer = timers[player];
if(timer.isActive && timer.isBattle && !timerCountDown(timer.creatureTimer, si->turnTimerInfo.creatureTimer, player, waitTime))
{
if(!turnTimerUpdateApplier(timers[state.color], waitTime))
if(isPvpBattle())
{
if(timers[state.color].battleTimer > 0)
if(timer.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.side = side;
if(isTactisPhase)
doNothing.actionType = EActionType::END_TACTIC_PHASE;
else
{
doNothing.actionType = EActionType::DEFEND;
doNothing.side = stack->unitSide();
doNothing.stackNumber = stack->unitId();
gameHandler.battles->makePlayerBattleAction(state.color, doNothing);
}
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);
}
}
}

View File

@ -10,11 +10,12 @@
#pragma once
#include "../lib/TurnTimerInfo.h"
VCMI_LIB_NAMESPACE_BEGIN
class CStack;
class PlayerColor;
struct TurnTimerInfo;
VCMI_LIB_NAMESPACE_END
@ -27,14 +28,24 @@ class TurnTimerHandler
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);
};

View File

@ -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)

View File

@ -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