/* * TurnTimerHandler.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */ #include "StdInc.h" #include "TurnTimerHandler.h" #include "CGameHandler.h" #include "battles/BattleProcessor.h" #include "queries/QueriesProcessor.h" #include "processors/TurnOrderProcessor.h" #include "../lib/battle/BattleInfo.h" #include "../lib/gameState/CGameState.h" #include "../lib/networkPacks/PacksForClient.h" #include "../lib/networkPacks/PacksForClientBattle.h" #include "../lib/CPlayerState.h" #include "../lib/CStack.h" #include "../lib/StartInfo.h" TurnTimerHandler::TurnTimerHandler(CGameHandler & gh): gameHandler(gh) { } void TurnTimerHandler::onGameplayStart(PlayerColor player) { if(const auto * si = gameHandler.getStartInfo()) { timers[player] = si->turnTimerInfo; timers[player].turnTimer = 0; timers[player].battleTimer = 0; timers[player].unitTimer = 0; timers[player].isActive = true; timers[player].isBattle = false; lastUpdate[player] = std::numeric_limits::max(); endTurnAllowed[player] = true; } } void TurnTimerHandler::setTimerEnabled(PlayerColor player, bool enabled) { assert(player.isValidPlayer()); timers[player].isActive = enabled; sendTimerUpdate(player); } void TurnTimerHandler::setEndTurnAllowed(PlayerColor player, bool enabled) { assert(player.isValidPlayer()); endTurnAllowed[player] = enabled; } 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) { if(const auto * si = gameHandler.getStartInfo()) { if(si->turnTimerInfo.isEnabled()) { endTurnAllowed[player] = true; auto & timer = timers[player]; if(si->turnTimerInfo.accumulatingTurnTimer) timer.baseTimer += timer.turnTimer; timer.turnTimer = si->turnTimerInfo.turnTimer; sendTimerUpdate(player); } } } void TurnTimerHandler::prolongTimers(int durationMs) { for (auto & timer : timers) timer.second.baseTimer += durationMs; } void TurnTimerHandler::update(int waitTimeMs) { if(!gameHandler.getStartInfo()->turnTimerInfo.isEnabled()) return; for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) if(gameHandler.gameState()->isPlayerMakingTurn(player)) onPlayerMakingTurn(player, waitTimeMs); // create copy for iterations - battle might end during onBattleLoop call std::vector ongoingBattles; for (auto & battle : gameHandler.gameState()->currentBattles) ongoingBattles.push_back(battle->battleID); for (auto & battleID : ongoingBattles) onBattleLoop(battleID, waitTimeMs); } bool TurnTimerHandler::timerCountDown(int & timer, int initialTimer, PlayerColor player, int waitTime) { if(timer > 0) { timer -= waitTime; lastUpdate[player] += waitTime; if(lastUpdate[player] >= turnTimePropagateFrequency) sendTimerUpdate(player); return true; } return false; } void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime) { const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs || !si->turnTimerInfo.isEnabled()) return; auto & timer = timers[player]; const auto * state = gameHandler.getPlayerState(player); if(state && state->human && timer.isActive && !timer.isBattle && state->status == EPlayerStatus::INGAME) { // turn timers are only used if turn timer is non-zero if (si->turnTimerInfo.turnTimer == 0) return; if(timerCountDown(timer.turnTimer, si->turnTimerInfo.turnTimer, player, waitTime)) return; if(timerCountDown(timer.baseTimer, si->turnTimerInfo.baseTimer, player, waitTime)) return; if(endTurnAllowed[state->color] && !gameHandler.queries->topQuery(state->color)) //wait for replies to avoid pending queries gameHandler.turnOrder->onPlayerEndsTurn(state->color); } } bool TurnTimerHandler::isPvpBattle(const BattleID & battleID) const { const auto * gs = gameHandler.gameState(); auto attacker = gs->getBattle(battleID)->getSidePlayer(BattleSide::ATTACKER); auto defender = gs->getBattle(battleID)->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(const BattleID & battleID) { const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs) return; auto attacker = gs->getBattle(battleID)->getSidePlayer(BattleSide::ATTACKER); auto defender = gs->getBattle(battleID)->getSidePlayer(BattleSide::DEFENDER); bool pvpBattle = isPvpBattle(battleID); for(auto i : {attacker, defender}) { if(i.isValidPlayer()) { auto & timer = timers[i]; timer.isBattle = true; timer.isActive = si->turnTimerInfo.isBattleEnabled(); timer.battleTimer = si->turnTimerInfo.battleTimer; timer.unitTimer = (pvpBattle ? si->turnTimerInfo.unitTimer : 0); sendTimerUpdate(i); } } } void TurnTimerHandler::onBattleEnd(const BattleID & battleID) { const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs) { assert(0); return; } if (!si->turnTimerInfo.isBattleEnabled()) return; auto attacker = gs->getBattle(battleID)->getSidePlayer(BattleSide::ATTACKER); auto defender = gs->getBattle(battleID)->getSidePlayer(BattleSide::DEFENDER); for(auto i : {attacker, defender}) { if(i.isValidPlayer()) { auto & timer = timers[i]; timer.isBattle = false; timer.isActive = true; sendTimerUpdate(i); } } } void TurnTimerHandler::onBattleNextStack(const BattleID & battleID, const CStack & stack) { const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs || !gs->getBattle(battleID)) { assert(0); return; } if (!si->turnTimerInfo.isBattleEnabled()) return; if(isPvpBattle(battleID)) { auto player = stack.getOwner(); auto & timer = timers[player]; if(timer.accumulatingUnitTimer) timer.battleTimer += timer.unitTimer; timer.unitTimer = si->turnTimerInfo.unitTimer; sendTimerUpdate(player); } } void TurnTimerHandler::onBattleLoop(const BattleID & battleID, int waitTime) { const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs) { assert(0); return; } if (!si->turnTimerInfo.isBattleEnabled()) return; BattleSide side = BattleSide::NONE; const CStack * stack = nullptr; bool isTactisPhase = gs->getBattle(battleID)->battleTacticDist() > 0; if(isTactisPhase) side = gs->getBattle(battleID)->battleGetTacticsSide(); else { stack = gs->getBattle(battleID)->battleGetStackByID(gs->getBattle(battleID)->getActiveStackID()); if(!stack || !stack->getOwner().isValidPlayer()) return; side = stack->unitSide(); } auto player = gs->getBattle(battleID)->getSidePlayer(side); if(!player.isValidPlayer()) return; const auto * state = gameHandler.getPlayerState(player); assert(state && state->status == EPlayerStatus::INGAME); if(!state || state->status != EPlayerStatus::INGAME || !state->human) return; auto & timer = timers[player]; if(timer.isActive && timer.isBattle) { // in pvp battles, timers are only used if unit timer is non-zero if(isPvpBattle(battleID) && si->turnTimerInfo.unitTimer == 0) return; if (timerCountDown(timer.unitTimer, si->turnTimerInfo.unitTimer, player, waitTime)) return; if (timerCountDown(timer.battleTimer, si->turnTimerInfo.battleTimer, player, waitTime)) return; if (timerCountDown(timer.turnTimer, si->turnTimerInfo.turnTimer, player, waitTime)) return; if (timerCountDown(timer.baseTimer, si->turnTimerInfo.baseTimer, player, waitTime)) return; if(isPvpBattle(battleID)) { 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(battleID, player, doNothing); } else { // battle vs neutrals - no-op, let battle run till the end // once battle is over player turn will be over due to running out of timer on adventure map } } }