Browse Source

Merge pull request #3217 from IvanSavenko/simturns_ui_2

Simturns UI update
Ivan Savenko 1 year ago
parent
commit
7c8afffe68

BIN
Mods/vcmi/Sprites/lobby/checkboxBlueOff.png


BIN
Mods/vcmi/Sprites/lobby/checkboxBlueOn.png


+ 8 - 0
Mods/vcmi/Sprites/lobby/dropdown.json

@@ -0,0 +1,8 @@
+{
+	"basepath" : "lobby/",
+	"images" :
+	[
+		{ "frame" : 0, "file" : "dropdownNormal.png"},
+		{ "frame" : 1, "file" : "dropdownPressed.png"}
+	]
+}

BIN
Mods/vcmi/Sprites/lobby/dropdownNormal.png


BIN
Mods/vcmi/Sprites/lobby/dropdownPressed.png


+ 2 - 2
Mods/vcmi/config/vcmi/chinese.json

@@ -205,11 +205,11 @@
 	"vcmi.optionsTab.chessFieldBase.hover" : "额外计时器",
 	"vcmi.optionsTab.chessFieldTurn.hover" : "转动计时器",
 	"vcmi.optionsTab.chessFieldBattle.hover" : "战斗计时器",
-	"vcmi.optionsTab.chessFieldCreature.hover" : "堆栈计时器",
+	"vcmi.optionsTab.chessFieldUnit.hover" : "堆栈计时器",
 	"vcmi.optionsTab.chessFieldBase.help" : "当{转动计时器}达到零时开始倒计时。 它仅在游戏开始时设置一次。 当计时器达到零时,玩家的回合结束。",
 	"vcmi.optionsTab.chessFieldTurn.help" : "当玩家在冒险地图上开始回合时开始倒计时。 它在每回合开始时重置为其初始值。 任何未使用的回合时间将被添加到{额外计时器}(如果正在使用)中。",
 	"vcmi.optionsTab.chessFieldBattle.help" : "战斗期间当 {堆栈计时器} 达到0时进行倒计时。 每次战斗开始时都会重置为初始值。 如果计时器达到零,当前活动的堆栈将进行防御。",
-	"vcmi.optionsTab.chessFieldCreature.help" : "当玩家在战斗中为当前堆栈选择一个动作时开始倒计时。 堆栈操作完成后,它会重置为其初始值。",
+	"vcmi.optionsTab.chessFieldUnit.help" : "当玩家在战斗中为当前堆栈选择一个动作时开始倒计时。 堆栈操作完成后,它会重置为其初始值。",
 
 	// Custom victory conditions for H3 campaigns and HotA maps
 	"vcmi.map.victoryCondition.daysPassed.toOthers" : "敌人依然存活至今,你失败了!",

+ 32 - 4
Mods/vcmi/config/vcmi/english.json

@@ -236,17 +236,20 @@
 
 	"vcmi.optionsTab.turnOptions.hover" : "Turn Options",
 	"vcmi.optionsTab.turnOptions.help" : "Select turn timer and simultaneous turns options",
+	"vcmi.optionsTab.selectPreset" : "Preset",
 
 	"vcmi.optionsTab.chessFieldBase.hover" : "Base timer",
 	"vcmi.optionsTab.chessFieldTurn.hover" : "Turn timer",
 	"vcmi.optionsTab.chessFieldBattle.hover" : "Battle timer",
-	"vcmi.optionsTab.chessFieldCreature.hover" : "Unit timer",
+	"vcmi.optionsTab.chessFieldUnit.hover" : "Unit timer",
 	"vcmi.optionsTab.chessFieldBase.help" : "Used when {Turn Timer} reaches 0. Set once at game start. On reaching zero, ends current turn. Any ongoing combat with end with a loss.",
-	"vcmi.optionsTab.chessFieldTurn.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Leftover added to {Base Timer} at turn's end.",
+	"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Leftover added to {Base Timer} at turn's end.",
+	"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Any unspent time is lost",
 	"vcmi.optionsTab.chessFieldBattle.help" : "Used in battles with AI or in pvp combat when {Unit Timer} runs out. Reset at start of each combat.",
-	"vcmi.optionsTab.chessFieldCreature.help" : "Used when selecting unit action in pvp combat. Reset at start of each unit's turn.",
+	"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Used when selecting unit action in pvp combat. Leftover added to {Battle Timer} at end of unit turn.",
+	"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Used when selecting unit action in pvp combat. Reset at start of each unit's turn. Any unspent time is lost",
 
-	"vcmi.optionsTab.simturns" : "Simultaneous turns",
+	"vcmi.optionsTab.simturnsTitle" : "Simultaneous turns",
 	"vcmi.optionsTab.simturnsMin.hover" : "At least for",
 	"vcmi.optionsTab.simturnsMax.hover" : "At most for",
 	"vcmi.optionsTab.simturnsAI.hover" : "(Experimental) Simultaneous AI Turns",
@@ -254,6 +257,31 @@
 	"vcmi.optionsTab.simturnsMax.help" : "Play simultaneously for specified number of days or until contact with another player",
 	"vcmi.optionsTab.simturnsAI.help" : "{Simultaneous AI Turns}\nExperimental option. Allows AI players to act at the same time as human player when simultaneous turns are enabled.",
 	
+	"vcmi.optionsTab.turnTime.select"     : "Select turn timer preset",
+	"vcmi.optionsTab.turnTime.unlimited"  : "Unlimited turn time",
+	"vcmi.optionsTab.turnTime.classic.1"  : "Classic timer: 1 minute",
+	"vcmi.optionsTab.turnTime.classic.2"  : "Classic timer: 2 minutes",
+	"vcmi.optionsTab.turnTime.classic.5"  : "Classic timer: 5 minutes",
+	"vcmi.optionsTab.turnTime.classic.10" : "Classic timer: 10 minutes",
+	"vcmi.optionsTab.turnTime.classic.20" : "Classic timer: 20 minutes",
+	"vcmi.optionsTab.turnTime.classic.30" : "Classic timer: 30 minutes",
+	"vcmi.optionsTab.turnTime.chess.20"   : "Chess: 20:00 + 10:00 + 02:00 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.16"   : "Chess: 16:00 + 08:00 + 01:30 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.8"    : "Chess: 08:00 + 04:00 + 01:00 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.4"    : "Chess: 04:00 + 02:00 + 00:30 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.2"    : "Chess: 02:00 + 01:00 + 00:15 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.1"    : "Chess: 01:00 + 01:00 + 00:00 + 00:00",
+
+	"vcmi.optionsTab.simturns.select"         : "Select simultaneous turns preset",
+	"vcmi.optionsTab.simturns.none"           : "No simultaneous turns",
+	"vcmi.optionsTab.simturns.tillContactMax" : "Simturns: Until contact",
+	"vcmi.optionsTab.simturns.tillContact1"   : "Simturns: 1 week, break on contact",
+	"vcmi.optionsTab.simturns.tillContact2"   : "Simturns: 2 weeks, break on contact",
+	"vcmi.optionsTab.simturns.tillContact4"   : "Simturns: 1 month, break on contact",
+	"vcmi.optionsTab.simturns.blocked1"       : "Simturns: 1 week, contacts blocked",
+	"vcmi.optionsTab.simturns.blocked2"       : "Simturns: 2 weeks, contacts blocked",
+	"vcmi.optionsTab.simturns.blocked4"       : "Simturns: 1 month, contacts blocked",
+	
 	// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
 	// Using this information, VCMI will automatically select correct plural form for every possible amount
 	"vcmi.optionsTab.simturns.days.0" : " %d days",

+ 3 - 3
Mods/vcmi/config/vcmi/german.json

@@ -240,13 +240,13 @@
 	"vcmi.optionsTab.chessFieldBase.hover" : "Basis-Timer",
 	"vcmi.optionsTab.chessFieldTurn.hover" : "Spielzug-Timer",
 	"vcmi.optionsTab.chessFieldBattle.hover" : "Kampf-Timer",
-	"vcmi.optionsTab.chessFieldCreature.hover" : "Einheiten-Timer",
+	"vcmi.optionsTab.chessFieldUnit.hover" : "Einheiten-Timer",
 	"vcmi.optionsTab.chessFieldBase.help" : "Wird verwendet, wenn {Spielzug-Timer} 0 erreicht. Wird einmal zu Beginn des Spiels gesetzt. Bei Erreichen von Null wird der aktuelle Spielzug beendet. Jeder laufende Kampf endet mit einem Verlust.",
 	"vcmi.optionsTab.chessFieldTurn.help" : "Wird außerhalb des Kampfes verwendet oder wenn der {Kampf-Timer} abgelaufen ist. Wird jede Runde zurückgesetzt. Reste werden am Ende der Runde zum {Basis-Timer} hinzugefügt.",
 	"vcmi.optionsTab.chessFieldBattle.help" : "Wird in Kämpfen mit der KI oder im PvP-Kampf verwendet, wenn {Einheiten-Timer} abläuft. Wird zu Beginn eines jeden Kampfes zurückgesetzt.",
-	"vcmi.optionsTab.chessFieldCreature.help" : "Wird bei der Auswahl der Einheitenaktion im PvP-Kampf verwendet. Wird zu Beginn der Runde jeder Einheit zurückgesetzt.",
+	"vcmi.optionsTab.chessFieldUnit.help" : "Wird bei der Auswahl der Einheitenaktion im PvP-Kampf verwendet. Wird zu Beginn der Runde jeder Einheit zurückgesetzt.",
 
-	"vcmi.optionsTab.simturns" : "Simultane Züge",
+	"vcmi.optionsTab.simturnsTitle" : "Simultane Züge",
 	"vcmi.optionsTab.simturnsMin.hover" : "Zumindest für",
 	"vcmi.optionsTab.simturnsMax.hover" : "Höchstens für",
 	"vcmi.optionsTab.simturnsAI.hover" : "(Experimentell) Simultane KI Züge",

+ 2 - 2
Mods/vcmi/config/vcmi/russian.json

@@ -204,11 +204,11 @@
   "vcmi.optionsTab.chessFieldBase.hover" : "Время игрока",
   "vcmi.optionsTab.chessFieldTurn.hover" : "Время на ход",
   "vcmi.optionsTab.chessFieldBattle.hover" : "Время на битву",
-  "vcmi.optionsTab.chessFieldCreature.hover" : "Время на отряд",
+  "vcmi.optionsTab.chessFieldUnit.hover" : "Время на отряд",
 	"vcmi.optionsTab.chessFieldBase.help" : "Обратный отсчет начинается когда {время на ход} истекает. Устанавливается один раз в начале игры. По истечении времени игрок завершает ход.",
 	"vcmi.optionsTab.chessFieldTurn.help" : "Обратный отсчет начивается когда игрок начинает свой ход. В начале каждого хода устанавливается в иходное значение. Все неиспользованное время добавляется ко {времени игрока}, если оно используется.",
 	"vcmi.optionsTab.chessFieldBattle.help" : "Обратный отсчет начинается когда {время на отряд истекает}. В начале каждой битвы устанавливается в исходное значение. По истечении времени текущий отряд получает приказ защищаться.",
-	"vcmi.optionsTab.chessFieldCreature.help" : "Обратный отсчет начинается когда игрок получает получает контроль над отрядом во время битвы. Устанавливается в исходное значение всякий раз, когда отряд получает возможность действовать.",
+	"vcmi.optionsTab.chessFieldUnit.help" : "Обратный отсчет начинается когда игрок получает получает контроль над отрядом во время битвы. Устанавливается в исходное значение всякий раз, когда отряд получает возможность действовать.",
 
 	"mapObject.core.creatureBank.cyclopsStockpile.name" : "Хранилище циклопов",
 	"mapObject.core.creatureBank.dragonFlyHive.name" : "Улей летучих змиев",

+ 33 - 4
Mods/vcmi/config/vcmi/ukrainian.json

@@ -229,13 +229,17 @@
 	"vcmi.optionsTab.chessFieldBase.hover" : "Основний таймер",
 	"vcmi.optionsTab.chessFieldTurn.hover" : "Таймер ходу",
 	"vcmi.optionsTab.chessFieldBattle.hover" : "Таймер битви",
-	"vcmi.optionsTab.chessFieldCreature.hover" : "Таймер загону",
+	"vcmi.optionsTab.chessFieldUnit.hover" : "Таймер загону",
 	"vcmi.optionsTab.chessFieldBase.help" : "Встановлюється один раз на початку гри. Коли вичерпується, поточний хід буде перервано, поточна битва буде програна.",
-	"vcmi.optionsTab.chessFieldTurn.help" : "Використовується під час ходу. Встановлюється кожен хід. Залишок додається до {основного таймеру} у кінці ходу",
+	"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Використовується під час ходу. Встановлюється на початку ходу. Залишок додається до {основного таймеру}",
+	"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Використовується під час ходу. Встановлюється на початку ходу. Залишок часу буде втрачено",
 	"vcmi.optionsTab.chessFieldBattle.help" : "Використовується у боях з ШІ чи у боях з гравцями якщо {таймер загону} вичерпується. Встановлюється на початку кожного бою.",
-	"vcmi.optionsTab.chessFieldCreature.help" : "Використовується при обираннія дії загону у боях з гравцями. Встановлюється на початку кожної дії.",
+	"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Використовується при обираннія дії загону у боях з гравцями. Встановлюється на початку дії. Залишок додається до {таймеру битви}",
+	"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Використовується при обираннія дії загону у боях з гравцями. Встановлюється на початку дії. Залишок часу буде втрачено.",
+	
+	"vcmi.optionsTab.accumulate" : "Накопичувати",
 
-	"vcmi.optionsTab.simturns" : "Одночасні ходи",
+	"vcmi.optionsTab.simturnsTitle" : "Одночасні ходи",
 	"vcmi.optionsTab.simturnsMin.hover" : "Щонайменше",
 	"vcmi.optionsTab.simturnsMax.hover" : "Щонайбільше",
 	"vcmi.optionsTab.simturnsAI.hover" : "(Експериментально) Одночасні ходи ШІ",
@@ -243,6 +247,31 @@
 	"vcmi.optionsTab.simturnsMax.help" : "Грати одночасно обрану кількість днів чи до першого контакту з іншим гравцем",
 	"vcmi.optionsTab.simturnsAI.help" : "{Одночасні ходи ШІ}\nЕкспериментальна опція. Дозволяє гравцям-ШІ діяти одночасно с гравцями-людьми якщо одночасні ходи увімкнені.",
 	
+	"vcmi.optionsTab.turnTime.select"     : "Типові налаштування таймерів",
+	"vcmi.optionsTab.turnTime.unlimited"  : "Необмежений час ходу",
+	"vcmi.optionsTab.turnTime.classic.1"  : "Класичний таймер: 1 хвилина",
+	"vcmi.optionsTab.turnTime.classic.2"  : "Класичний таймер: 2 хвилини",
+	"vcmi.optionsTab.turnTime.classic.5"  : "Класичний таймер: 5 хвилин",
+	"vcmi.optionsTab.turnTime.classic.10" : "Класичний таймер: 10 хвилин",
+	"vcmi.optionsTab.turnTime.classic.20" : "Класичний таймер: 20 хвилин",
+	"vcmi.optionsTab.turnTime.classic.30" : "Класичний таймер: 30 хвилин",
+	"vcmi.optionsTab.turnTime.chess.20"   : "Шахи: 20:00 + 10:00 + 02:00 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.16"   : "Шахи: 16:00 + 08:00 + 01:30 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.8"    : "Шахи: 08:00 + 04:00 + 01:00 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.4"    : "Шахи: 04:00 + 02:00 + 00:30 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.2"    : "Шахи: 02:00 + 01:00 + 00:15 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.1"    : "Шахи: 01:00 + 01:00 + 00:00 + 00:00",
+
+	"vcmi.optionsTab.simturns.select"         : "Типові налаштування одночасних ходів",
+	"vcmi.optionsTab.simturns.none"           : "Без одночасних ходів",
+	"vcmi.optionsTab.simturns.tillContactMax" : "Одночасно: До контакту",
+	"vcmi.optionsTab.simturns.tillContact1"   : "Одночасно: 1 тиждень, до контакту",
+	"vcmi.optionsTab.simturns.tillContact2"   : "Одночасно: 2 тижні, до контакту",
+	"vcmi.optionsTab.simturns.tillContact4"   : "Одночасно: 1 місяць, до контакту",
+	"vcmi.optionsTab.simturns.blocked1"       : "Одночасно: 1 тиждень, без контактів",
+	"vcmi.optionsTab.simturns.blocked2"       : "Одночасно: 2 тижні, без контактів",
+	"vcmi.optionsTab.simturns.blocked4"       : "Одночасно: 1 місяць, без контактів",
+	
 	// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
 	// Using this information, VCMI will automatically select correct plural form for every possible amount
 	"vcmi.optionsTab.simturns.days.0" : " %d днів",

+ 2 - 2
Mods/vcmi/config/vcmi/vietnamese.json

@@ -201,11 +201,11 @@
   "vcmi.optionsTab.chessFieldBase.hover" : "Thời gian thêm",
   "vcmi.optionsTab.chessFieldTurn.hover" : "Thời gian lượt",
   "vcmi.optionsTab.chessFieldBattle.hover" : "Thời gian trận đánh",
-  "vcmi.optionsTab.chessFieldCreature.hover" : "Thời gian lính",
+  "vcmi.optionsTab.chessFieldUnit.hover" : "Thời gian lính",
   "vcmi.optionsTab.chessFieldBase.help": "Bắt đầu đếm ngược khi {Thời gian lượt} giảm đến 0. Được đặt 1 lần khi bắt đầu trò chơi. Khi thời gian này giảm đến 0, lượt của người chơi kết thúc.",
   "vcmi.optionsTab.chessFieldTurn.help": "Bắt đầu đếm ngược khi đến lượt người chơi trên bản đồ phiêu lưu. Nó được đặt lại giá trị ban đầu khi bắt đầu mỗi lượt. Thời gian lượt chưa sử dụng sẽ được thêm vào {Thời gian thêm} nếu có.",
   "vcmi.optionsTab.chessFieldBattle.help": "Đếm ngược trong suốt trận đánh khi {Thời gian lính} giảm đến 0. Nó được đặt lại giá trị ban đầu khi bắt đầu mỗi trận đánh. Nếu thời gian giảm đến 0, đội lính hiện tại sẽ phòng thủ.",
-  "vcmi.optionsTab.chessFieldCreature.help": "Bắt đầu đếm ngược khi người chơi đang chọn hành động cho đội linh hiện tại trong suốt trận đánh. Nó được đặt lại giá trị ban đầu sau khi hành động của đội lính hoàn tất.",
+  "vcmi.optionsTab.chessFieldUnit.help": "Bắt đầu đếm ngược khi người chơi đang chọn hành động cho đội linh hiện tại trong suốt trận đánh. Nó được đặt lại giá trị ban đầu sau khi hành động của đội lính hoàn tất.",
 
   "vcmi.map.victoryCondition.daysPassed.toOthers": "Đối thủ đã xoay xở để sinh tồn đến ngày này. Họ giành chiến thắng!",
   "vcmi.map.victoryCondition.daysPassed.toSelf": "Chúc mừng! Bạn đã vượt khó để sinh tồn. Chiến thắng thuộc về bạn!",

+ 1 - 1
client/NetPacksClient.cpp

@@ -901,7 +901,7 @@ void ApplyClientNetPackVisitor::visitPlayerEndsTurn(PlayerEndsTurn & pack)
 
 void ApplyClientNetPackVisitor::visitTurnTimeUpdate(TurnTimeUpdate & pack)
 {
-	logNetwork->debug("Server sets turn timer {turn: %d, base: %d, battle: %d, creature: %d} for %s", pack.turnTimer.turnTimer, pack.turnTimer.baseTimer, pack.turnTimer.battleTimer, pack.turnTimer.creatureTimer, pack.player.toString());
+	logNetwork->debug("Server sets turn timer {turn: %d, base: %d, battle: %d, creature: %d} for %s", pack.turnTimer.turnTimer, pack.turnTimer.baseTimer, pack.turnTimer.battleTimer, pack.turnTimer.unitTimer, pack.player.toString());
 }
 
 void ApplyClientNetPackVisitor::visitPlayerMessageClient(PlayerMessageClient & pack)

+ 2 - 2
client/adventureMap/TurnTimerWidget.cpp

@@ -128,9 +128,9 @@ void TurnTimerWidget::updateTimer(PlayerColor player, uint32_t msPassed)
 	if(player.isValidPlayer() || (playerInfo && playerInfo->isHuman()))
 	{
 		if(time.isBattle)
-			timeCheckAndUpdate(time.creatureTimer);
+			timeCheckAndUpdate(time.baseTimer + time.turnTimer + time.battleTimer + time.unitTimer);
 		else
-			timeCheckAndUpdate(time.turnTimer);
+			timeCheckAndUpdate(time.baseTimer + time.turnTimer);
 	}
 	else
 		timeCheckAndUpdate(0);

+ 3 - 3
client/gui/InterfaceObjectConfigurable.cpp

@@ -310,8 +310,6 @@ std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNo
 	auto image = ImagePath::fromJson(config["image"]);
 	auto position = readPosition(config["position"]);
 	auto pic = std::make_shared<CPicture>(image, position.x, position.y);
-	if(!config["visible"].isNull())
-		pic->visible = config["visible"].Bool();
 
 	if ( config["playerColored"].Bool() && LOCPLINT)
 		pic->colorize(LOCPLINT->playerID);
@@ -562,9 +560,11 @@ std::shared_ptr<ComboBox> InterfaceObjectConfigurable::buildComboBox(const JsonN
 {
 	logGlobal->debug("Building widget ComboBox");
 	auto position = readPosition(config["position"]);
+	auto dropDownPosition = readPosition(config["dropDownPosition"]);
 	auto image = AnimationPath::fromJson(config["image"]);
 	auto help = readHintText(config["help"]);
-	auto result = std::make_shared<ComboBox>(position, image, help, config["dropDown"]);
+	auto result = std::make_shared<ComboBox>(position, image, help, config["dropDown"], dropDownPosition);
+
 	if(!config["items"].isNull())
 	{
 		for(const auto & item : config["items"].Vector())

+ 9 - 1
client/lobby/OptionsTab.cpp

@@ -42,8 +42,16 @@
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/mapping/CMapHeader.h"
 
+static JsonPath optionsTabConfigLocation()
+{
+	if(settings["general"]["enableUiEnhancements"].Bool())
+		return JsonPath::builtin("config/widgets/playerOptionsTab.json");
+	else
+		return JsonPath::builtin("config/widgets/advancedOptionsTab.json");
+}
+
 OptionsTab::OptionsTab()
-	: OptionsTabBase(JsonPath::builtin("config/widgets/playerOptionsTab.json"))
+	: OptionsTabBase(optionsTabConfigLocation())
 	, humanPlayers(0)
 {
 }

+ 112 - 19
client/lobby/OptionsTabBase.cpp

@@ -22,22 +22,53 @@
 #include "../../lib/MetaString.h"
 #include "../../lib/CGeneralTextHandler.h"
 
+std::vector<TurnTimerInfo> OptionsTabBase::getTimerPresets() const
+{
+	std::vector<TurnTimerInfo> result;
+
+	for (auto const & tpreset : variables["timerPresets"].Vector())
+	{
+		TurnTimerInfo tinfo;
+		tinfo.baseTimer = tpreset[0].Integer() * 1000;
+		tinfo.turnTimer = tpreset[1].Integer() * 1000;
+		tinfo.battleTimer = tpreset[2].Integer() * 1000;
+		tinfo.unitTimer = tpreset[3].Integer() * 1000;
+		tinfo.accumulatingTurnTimer = tpreset[4].Bool();
+		tinfo.accumulatingUnitTimer = tpreset[5].Bool();
+		result.push_back(tinfo);
+	}
+	return result;
+}
+
+std::vector<SimturnsInfo> OptionsTabBase::getSimturnsPresets() const
+{
+	std::vector<SimturnsInfo> result;
+
+	for (auto const & tpreset : variables["simturnsPresets"].Vector())
+	{
+		SimturnsInfo tinfo;
+		tinfo.optionalTurns = tpreset[0].Integer();
+		tinfo.requiredTurns = tpreset[1].Integer();
+		tinfo.allowHumanWithAI = tpreset[2].Bool();
+		result.push_back(tinfo);
+	}
+	return result;
+}
+
 OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
 {
 	recActions = 0;
 
-	addCallback("setTimerPreset", [&](int index){
-		if(!variables["timerPresets"].isNull())
-		{
-			auto tpreset = variables["timerPresets"].Vector().at(index).Vector();
-			TurnTimerInfo tinfo;
-			tinfo.baseTimer = tpreset.at(0).Integer() * 1000;
-			tinfo.turnTimer = tpreset.at(1).Integer() * 1000;
-			tinfo.battleTimer = tpreset.at(2).Integer() * 1000;
-			tinfo.creatureTimer = tpreset.at(3).Integer() * 1000;
-			CSH->setTurnTimerInfo(tinfo);
-		}
-	});
+	auto setTimerPresetCallback = [this](int index){
+		CSH->setTurnTimerInfo(getTimerPresets().at(index));
+	};
+
+	auto setSimturnsPresetCallback = [this](int index){
+		CSH->setSimturnsInfo(getSimturnsPresets().at(index));
+	};
+
+	addCallback("setTimerPreset", setTimerPresetCallback);
+	addCallback("setSimturnPreset", setSimturnsPresetCallback);
 
 	addCallback("setSimturnDurationMin", [&](int index){
 		SimturnsInfo info = SEL->getStartInfo()->simturnsInfo;
@@ -59,6 +90,18 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
 		CSH->setSimturnsInfo(info);
 	});
 
+	addCallback("setTurnTimerAccumulate", [&](int index){
+		TurnTimerInfo info = SEL->getStartInfo()->turnTimerInfo;
+		info.accumulatingTurnTimer = index;
+		CSH->setTurnTimerInfo(info);
+	});
+
+	addCallback("setUnitTimerAccumulate", [&](int index){
+		TurnTimerInfo info = SEL->getStartInfo()->turnTimerInfo;
+		info.accumulatingUnitTimer = index;
+		CSH->setTurnTimerInfo(info);
+	});
+
 	//helper function to parse string containing time to integer reflecting time in seconds
 	//assumed that input string can be modified by user, function shall support user's intention
 	// normal: 2:00, 12:30
@@ -128,12 +171,12 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
 			CSH->setTurnTimerInfo(tinfo);
 		}
 	});
-	addCallback("parseAndSetTimer_creature", [parseTimerString](const std::string & str){
+	addCallback("parseAndSetTimer_unit", [parseTimerString](const std::string & str){
 		int time = parseTimerString(str) * 1000;
 		if(time >= 0)
 		{
 			TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo;
-			tinfo.creatureTimer = time;
+			tinfo.unitTimer = time;
 			CSH->setTurnTimerInfo(tinfo);
 		}
 	});
@@ -175,7 +218,7 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
 						tinfo.baseTimer = (*tObj)["default"].Vector().at(0).Integer() * 1000;
 						tinfo.turnTimer = (*tObj)["default"].Vector().at(1).Integer() * 1000;
 						tinfo.battleTimer = (*tObj)["default"].Vector().at(2).Integer() * 1000;
-						tinfo.creatureTimer = (*tObj)["default"].Vector().at(3).Integer() * 1000;
+						tinfo.unitTimer = (*tObj)["default"].Vector().at(3).Integer() * 1000;
 						CSH->setTurnTimerInfo(tinfo);
 					}
 				}
@@ -194,6 +237,34 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath)
 
 		w->setItem(0);
 	}
+
+	if(auto w = widget<ComboBox>("simturnsPresetSelector"))
+	{
+		w->onConstructItems = [this](std::vector<const void *> & curItems)
+		{
+			for (size_t i = 0; i < variables["simturnsPresets"].Vector().size(); ++i)
+				curItems.push_back((void*)i);
+		};
+
+		w->onSetItem = [setSimturnsPresetCallback](const void * item){
+			size_t itemIndex = (size_t)item;
+			setSimturnsPresetCallback(itemIndex);
+		};
+	}
+
+	if(auto w = widget<ComboBox>("timerPresetSelector"))
+	{
+		w->onConstructItems = [this](std::vector<const void *> & curItems)
+		{
+			for (size_t i = 0; i < variables["timerPresets"].Vector().size(); ++i)
+				curItems.push_back((void*)i);
+		};
+
+		w->onSetItem = [setTimerPresetCallback](const void * item){
+			size_t itemIndex = (size_t)item;
+			setTimerPresetCallback(itemIndex);
+		};
+	}
 }
 
 void OptionsTabBase::recreate()
@@ -246,12 +317,34 @@ void OptionsTabBase::recreate()
 	if(auto buttonSimturnsAI = widget<CToggleButton>("buttonSimturnsAI"))
 		buttonSimturnsAI->setSelectedSilent(SEL->getStartInfo()->simturnsInfo.allowHumanWithAI);
 
+	if(auto buttonTurnTimerAccumulate = widget<CToggleButton>("buttonTurnTimerAccumulate"))
+		buttonTurnTimerAccumulate->setSelectedSilent(SEL->getStartInfo()->turnTimerInfo.accumulatingTurnTimer);
+
+	if(auto chessFieldTurnLabel = widget<CLabel>("chessFieldTurnLabel"))
+	{
+		if (SEL->getStartInfo()->turnTimerInfo.accumulatingTurnTimer)
+			chessFieldTurnLabel->setText(CGI->generaltexth->translate("vcmi.optionsTab.chessFieldTurnAccumulate.help"));
+		else
+			chessFieldTurnLabel->setText(CGI->generaltexth->translate("vcmi.optionsTab.chessFieldTurnDiscard.help"));
+	}
+
+	if(auto chessFieldUnitLabel = widget<CLabel>("chessFieldUnitLabel"))
+	{
+		if (SEL->getStartInfo()->turnTimerInfo.accumulatingUnitTimer)
+			chessFieldUnitLabel->setText(CGI->generaltexth->translate("vcmi.optionsTab.chessFieldUnitAccumulate.help"));
+		else
+			chessFieldUnitLabel->setText(CGI->generaltexth->translate("vcmi.optionsTab.chessFieldUnitDiscard.help"));
+	}
+
+	if(auto buttonUnitTimerAccumulate = widget<CToggleButton>("buttonUnitTimerAccumulate"))
+		buttonUnitTimerAccumulate->setSelectedSilent(SEL->getStartInfo()->turnTimerInfo.accumulatingUnitTimer);
+
 	const auto & turnTimerRemote = SEL->getStartInfo()->turnTimerInfo;
 
 	//classic timer
 	if(auto turnSlider = widget<CSlider>("sliderTurnDuration"))
 	{
-		if(!variables["timerPresets"].isNull() && !turnTimerRemote.battleTimer && !turnTimerRemote.creatureTimer && !turnTimerRemote.baseTimer)
+		if(!variables["timerPresets"].isNull() && !turnTimerRemote.battleTimer && !turnTimerRemote.unitTimer && !turnTimerRemote.baseTimer)
 		{
 			for(int idx = 0; idx < variables["timerPresets"].Vector().size(); ++idx)
 			{
@@ -280,12 +373,12 @@ void OptionsTabBase::recreate()
 		ww->setText(timeToString(turnTimerRemote.turnTimer), false);
 	if(auto ww = widget<CTextInput>("chessFieldBattle"))
 		ww->setText(timeToString(turnTimerRemote.battleTimer), false);
-	if(auto ww = widget<CTextInput>("chessFieldCreature"))
-		ww->setText(timeToString(turnTimerRemote.creatureTimer), false);
+	if(auto ww = widget<CTextInput>("chessFieldUnit"))
+		ww->setText(timeToString(turnTimerRemote.unitTimer), false);
 
 	if(auto w = widget<ComboBox>("timerModeSwitch"))
 	{
-		if(turnTimerRemote.battleTimer || turnTimerRemote.creatureTimer || turnTimerRemote.baseTimer)
+		if(turnTimerRemote.battleTimer || turnTimerRemote.unitTimer || turnTimerRemote.baseTimer)
 		{
 			if(auto turnSlider = widget<CSlider>("sliderTurnDuration"))
 				if(turnSlider->isActive())

+ 10 - 0
client/lobby/OptionsTabBase.h

@@ -12,9 +12,19 @@
 #include "../gui/InterfaceObjectConfigurable.h"
 #include "../../lib/filesystem/ResourcePath.h"
 
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct TurnTimerInfo;
+struct SimturnsInfo;
+
+VCMI_LIB_NAMESPACE_END
+
 /// The options tab which is shown at the map selection phase.
 class OptionsTabBase : public InterfaceObjectConfigurable
 {
+	std::vector<TurnTimerInfo> getTimerPresets() const;
+	std::vector<SimturnsInfo> getSimturnsPresets() const;
+
 public:
 	OptionsTabBase(const JsonPath & configPath);
 

+ 27 - 23
client/widgets/ComboBox.cpp

@@ -22,19 +22,20 @@ ComboBox::DropDown::Item::Item(const JsonNode & config, ComboBox::DropDown & _dr
 {
 	build(config);
 	
-	if(auto w = widget<CPicture>("hoverImage"))
+	if(auto w = widget<CIntObject>("hoverImage"))
 	{
 		pos.w = w->pos.w;
 		pos.h = w->pos.h;
+		w->disable();
 	}
 	setRedrawParent(true);
 }
 
 void ComboBox::DropDown::Item::updateItem(int idx, const void * _item)
 {
+	item = _item;
 	if(auto w = widget<CLabel>("labelName"))
 	{
-		item = _item;
 		if(dropDown.comboBox.getItemText)
 			w->setText(dropDown.comboBox.getItemText(idx, item));
 	}
@@ -42,14 +43,14 @@ void ComboBox::DropDown::Item::updateItem(int idx, const void * _item)
 
 void ComboBox::DropDown::Item::hover(bool on)
 {
-	auto h = widget<CPicture>("hoverImage");
+	auto h = widget<CIntObject>("hoverImage");
 	auto w = widget<CLabel>("labelName");
 	if(h && w)
 	{
-		if(w->getText().empty())
-			h->visible = false;
+		if(w->getText().empty() || on == false)
+			h->disable();
 		else
-			h->visible = on;
+			h->enable();
 	}
 	redraw();
 }
@@ -66,7 +67,7 @@ void ComboBox::DropDown::Item::clickReleased(const Point & cursorPosition)
 	dropDown.clickReleased(cursorPosition);
 }
 
-ComboBox::DropDown::DropDown(const JsonNode & config, ComboBox & _comboBox):
+ComboBox::DropDown::DropDown(const JsonNode & config, ComboBox & _comboBox, Point dropDownPosition):
 	InterfaceObjectConfigurable(LCLICK | HOVER),
 	comboBox(_comboBox)
 {
@@ -77,7 +78,7 @@ ComboBox::DropDown::DropDown(const JsonNode & config, ComboBox & _comboBox):
 	
 	addCallback("sliderMove", std::bind(&ComboBox::DropDown::sliderMove, this, std::placeholders::_1));
 	
-	pos = comboBox.pos;
+	pos = comboBox.pos + dropDownPosition;
 	
 	build(config);
 	
@@ -129,20 +130,21 @@ void ComboBox::DropDown::clickPressed(const Point & cursorPosition)
 
 void ComboBox::DropDown::updateListItems()
 {
+	int elemIdx = 0;
+
 	if(auto w = widget<CSlider>("slider"))
+		elemIdx = w->getValue();
+
+	for(auto item : items)
 	{
-		int elemIdx = w->getValue();
-		for(auto item : items)
+		if(elemIdx < curItems.size())
+		{
+			item->updateItem(elemIdx, curItems[elemIdx]);
+			elemIdx++;
+		}
+		else
 		{
-			if(elemIdx < curItems.size())
-			{
-				item->updateItem(elemIdx, curItems[elemIdx]);
-				elemIdx++;
-			}
-			else
-			{
-				item->updateItem(elemIdx);
-			}
+			item->updateItem(elemIdx);
 		}
 	}
 }
@@ -155,18 +157,20 @@ void ComboBox::DropDown::setItem(const void * item)
 	GH.windows().popWindows(1);
 }
 
-ComboBox::ComboBox(Point position, const AnimationPath & defName, const std::pair<std::string, std::string> & help, const JsonNode & dropDownDescriptor, EShortcut key, bool playerColoredButton):
+ComboBox::ComboBox(Point position, const AnimationPath & defName, const std::pair<std::string, std::string> & help, const JsonNode & dropDownDescriptor, Point dropDownPosition, EShortcut key, bool playerColoredButton):
 	CButton(position, defName, help, 0, key, playerColoredButton)
 {
-	addCallback([&, dropDownDescriptor]()
+	addCallback([this, dropDownDescriptor, dropDownPosition]()
 	{
-		GH.windows().createAndPushWindow<ComboBox::DropDown>(dropDownDescriptor, *this);
+		GH.windows().createAndPushWindow<ComboBox::DropDown>(dropDownDescriptor, *this, dropDownPosition);
 	});
 }
 
 void ComboBox::setItem(const void * item)
 {
-	if(auto w = std::dynamic_pointer_cast<CLabel>(overlay); getItemText)
+	auto w = std::dynamic_pointer_cast<CLabel>(overlay);
+
+	if( w && getItemText)
 		addTextOverlay(getItemText(0, item), w->font, w->color);
 	
 	if(onSetItem)

+ 6 - 3
client/widgets/ComboBox.h

@@ -32,17 +32,18 @@ class ComboBox : public CButton
 		friend struct Item;
 		
 	public:
-		DropDown(const JsonNode &, ComboBox &);
+		DropDown(const JsonNode &, ComboBox &, Point dropDownPosition);
 		
 		bool receiveEvent(const Point & position, int eventType) const override;
 		void clickPressed(const Point & cursorPosition) override;
 		void setItem(const void *);
+
+		void updateListItems();
 			
 	private:
 		std::shared_ptr<DropDown::Item> buildItem(const JsonNode & config);
 		
 		void sliderMove(int slidPos);
-		void updateListItems();
 		
 		ComboBox & comboBox;
 		std::vector<std::shared_ptr<Item>> items;
@@ -54,7 +55,7 @@ class ComboBox : public CButton
 	void setItem(const void *);
 
 public:
-	ComboBox(Point position, const AnimationPath & defName, const std::pair<std::string, std::string> & help, const JsonNode & dropDownDescriptor, EShortcut key = {}, bool playerColoredButton = false);
+	ComboBox(Point position, const AnimationPath & defName, const std::pair<std::string, std::string> & help, const JsonNode & dropDownDescriptor, Point dropDownPosition, EShortcut key = {}, bool playerColoredButton = false);
 	
 	//define this callback to fill input vector with data for the combo box
 	std::function<void(std::vector<const void *> &)> onConstructItems;
@@ -66,4 +67,6 @@ public:
 	std::function<std::string(int, const void *)> getItemText;
 	
 	void setItem(int id);
+
+	void updateListItems();
 };

+ 2 - 4
client/widgets/Images.cpp

@@ -35,7 +35,6 @@
 
 CPicture::CPicture(std::shared_ptr<IImage> image, const Point & position)
 	: bg(image)
-	, visible(true)
 	, needRefresh(false)
 {
 	pos += position;
@@ -53,7 +52,6 @@ CPicture::CPicture( const ImagePath & bmpname )
 
 CPicture::CPicture( const ImagePath & bmpname, const Point & position )
 	: bg(GH.renderHandler().loadImage(bmpname))
-	, visible(true)
 	, needRefresh(false)
 {
 	pos.x += position.x;
@@ -81,13 +79,13 @@ CPicture::CPicture(std::shared_ptr<IImage> image, const Rect &SrcRect, int x, in
 
 void CPicture::show(Canvas & to)
 {
-	if (visible && needRefresh)
+	if (needRefresh)
 		showAll(to);
 }
 
 void CPicture::showAll(Canvas & to)
 {
-	if(bg && visible)
+	if(bg)
 	{
 		if (srcRect.has_value())
 			to.draw(bg, pos.topLeft(), *srcRect);

+ 0 - 4
client/widgets/Images.h

@@ -34,10 +34,6 @@ public:
 	/// If set to true, iamge will be redrawn on each frame
 	bool needRefresh;
 
-	/// If set to false, image will not be rendered
-	/// Deprecated, use CIntObject::disable()/enable() instead
-	bool visible;
-
 	std::shared_ptr<IImage> getSurface()
 	{
 		return bg;

+ 10 - 2
client/windows/CSpellWindow.cpp

@@ -449,8 +449,16 @@ void CSpellWindow::setCurrentPage(int value)
 	schoolPicture->visible = selectedTab!=4 && currentPage == 0;
 	if(selectedTab != 4)
 		schoolPicture->setFrame(selectedTab, 0);
-	leftCorner->visible = currentPage != 0;
-	rightCorner->visible = (currentPage+1) < pagesWithinCurrentTab();
+
+	if (currentPage != 0)
+		leftCorner->enable();
+	else
+		leftCorner->disable();
+
+	if (currentPage + 1 < pagesWithinCurrentTab())
+		rightCorner->enable();
+	else
+		rightCorner->disable();
 
 	mana->setText(std::to_string(myHero->mana));//just in case, it will be possible to cast spell without closing book
 }

+ 129 - 0
config/widgets/advancedOptionsTab.json

@@ -0,0 +1,129 @@
+{
+	"items":
+	[
+		{
+			"name": "background",
+			"type": "picture",
+			"image": "ADVOPTBK",
+			"position": {"x": 0, "y": 6}
+		},
+
+		{
+			"name": "labelTitle",
+			"type": "label",
+			"font": "big",
+			"alignment": "center",
+			"color": "yellow",
+			"text": "core.genrltxt.515",
+			"position": {"x": 222, "y": 36}
+		},
+		
+		{
+			"name": "labelSubTitle",
+			"type": "multiLineLabel",
+			"font": "small",
+			"alignment": "center",
+			"color": "white",
+			"text": "core.genrltxt.516",
+			"rect": {"x": 60, "y": 50, "w": 320, "h": 0},
+			"adoptHeight": true
+		},
+		
+		{
+			"name": "labelPlayerNameAndHandicap",
+			"type": "multiLineLabel",
+			"font": "small",
+			"alignment": "center",
+			"color": "yellow",
+			"text": "core.genrltxt.517",
+			"rect": {"x": 58, "y": 92, "w": 100, "h": 0},
+			"adoptHeight": true
+		},
+		
+		{
+			"name": "labelStartingTown",
+			"type": "multiLineLabel",
+			"font": "small",
+			"alignment": "center",
+			"color": "yellow",
+			"text": "core.genrltxt.518",
+			"rect": {"x": 163, "y": 92, "w": 70, "h": 0},
+			"adoptHeight": true
+		},
+		
+		{
+			"name": "labelStartingHero",
+			"type": "multiLineLabel",
+			"font": "small",
+			"alignment": "center",
+			"color": "yellow",
+			"text": "core.genrltxt.519",
+			"rect": {"x": 239, "y": 92, "w": 70, "h": 0},
+			"adoptHeight": true
+		},
+		
+		{
+			"name": "labelStartingBonus",
+			"type": "multiLineLabel",
+			"font": "small",
+			"alignment": "center",
+			"color": "yellow",
+			"text": "core.genrltxt.520",
+			"rect": {"x": 315, "y": 92, "w": 70, "h": 0},
+			"adoptHeight": true
+		},
+		
+		// timer
+		{
+			"type": "label",
+			"font": "small",
+			"alignment": "center",
+			"color": "yellow",
+			"text": "core.genrltxt.521",
+			"position": {"x": 222, "y": 544}
+		},
+		
+		{
+			"name": "labelTurnDurationValue",
+			"type": "label",
+			"font": "small",
+			"alignment": "center",
+			"color": "white",
+			"text": "",
+			"position": {"x": 319, "y": 565}
+		},
+		
+		{
+			"name": "sliderTurnDuration",
+			"type": "slider",
+			"orientation": "horizontal",
+			"position": {"x": 55, "y": 557},
+			"size": 194,
+			"callback": "setTimerPreset",
+			"itemsVisible": 1,
+			"itemsTotal": 11,
+			"selected": 11,
+			"style": "blue",
+			"scrollBounds": {"x": -3, "y": -25, "w": 337, "h": 43},
+			"panningStep": 20
+		},
+	],
+
+	"variables":
+	{
+		"timerPresets" :
+		[
+			[0,   60, 0, 0, false, false],
+			[0,  120, 0, 0, false, false],
+			[0,  240, 0, 0, false, false],
+			[0,  360, 0, 0, false, false],
+			[0,  480, 0, 0, false, false],
+			[0,  600, 0, 0, false, false],
+			[0,  900, 0, 0, false, false],
+			[0, 1200, 0, 0, false, false],
+			[0, 1500, 0, 0, false, false],
+			[0, 1800, 0, 0, false, false],
+			[0,    0, 0, 0, false, false],
+		]
+	}
+}

+ 35 - 43
config/widgets/playerOptionsTab.json

@@ -1,4 +1,5 @@
 {
+	"library" : "config/widgets/turnOptionsDropdownLibrary.json",
 	"items":
 	[
 		{
@@ -72,58 +73,49 @@
 			"rect": {"x": 315, "y": 92, "w": 70, "h": 0},
 			"adoptHeight": true
 		},
-		
-		// timer
-		{
-			"type": "label",
-			"font": "small",
-			"alignment": "center",
-			"color": "yellow",
-			"text": "core.genrltxt.521",
-			"position": {"x": 222, "y": 544}
-		},
-		
+
 		{
-			"name": "labelTurnDurationValue",
-			"type": "label",
-			"font": "small",
-			"alignment": "center",
-			"color": "white",
-			"text": "",
-			"position": {"x": 319, "y": 565}
+			"type" : "dropDownTimers",
+			"name": "timerPresetSelector",
+			"position": {"x": 56, "y": 535},
+			"dropDownPosition": {"x": 0, "y": -260}
 		},
-		
 		{
-			"name": "sliderTurnDuration",
-			"type": "slider",
-			"orientation": "horizontal",
-			"position": {"x": 55, "y": 557},
-			"size": 194,
-			"callback": "setTimerPreset",
-			"itemsVisible": 1,
-			"itemsTotal": 11,
-			"selected": 11,
-			"style": "blue",
-			"scrollBounds": {"x": -3, "y": -25, "w": 337, "h": 43},
-			"panningStep": 20
-		},
+			"type" : "dropDownSimturns",
+			"name": "simturnsPresetSelector",
+			"position": {"x": 56, "y": 555},
+			"dropDownPosition": {"x": 0, "y": -160}
+		}
 	],
 
 	"variables":
 	{
 		"timerPresets" :
 		[
-			[0, 60, 0, 0],
-			[0, 120, 0, 0],
-			[0, 240, 0, 0],
-			[0, 360, 0, 0],
-			[0, 480, 0, 0],
-			[0, 600, 0, 0],
-			[0, 900, 0, 0],
-			[0, 1200, 0, 0],
-			[0, 1500, 0, 0],
-			[0, 1800, 0, 0],
-			[0, 0, 0, 0],
+			[    0,    0,   0, 0, false, false],
+			[    0,   60,   0, 0, false, false],
+			[    0,  120,   0, 0, false, false],
+			[    0,  300,   0, 0, false, false],
+			[    0,  600,   0, 0, false, false],
+			[    0, 1200,   0, 0, false, false],
+			[    0, 1800,   0, 0, false, false],
+			[  960,  480, 120, 0, true, false],
+			[  960,  480,  75, 0, true, false],
+			[  480,  240,  60, 0, true, false],
+			[  120,   90,  60, 0, true, false],
+			[  120,   60,  15, 0, true, false],
+			[   60,   60,   0, 0, true, false]
+		],
+		"simturnsPresets" :
+		[
+			[   0,  0, false],
+			[ 999,  0, false],
+			[   7,  0, false],
+			[  14,  0, false],
+			[  28,  0, false],
+			[   7,  7, false],
+			[  14, 14, false],
+			[  28, 28, false]
 		]
 	}
 }

+ 520 - 0
config/widgets/turnOptionsDropdownLibrary.json

@@ -0,0 +1,520 @@
+{
+	"dropDownBackground" : 
+	{
+		"type": "transparentFilledRectangle",
+		"visible": false,
+		"rect": {"x": 1, "y": 1, "w": 219, "h": 19},
+		"color": [0, 0, 0, 128],
+		"colorLine": [0, 0, 0, 128]
+	},
+	"dropDownLabel":
+	{
+		"type": "label",
+		"font": "small",
+		"alignment": "left",
+		"color": "white",
+		"position": {"x": 4, "y": 0}
+	},
+	"dropDownHover":
+	{
+		"type": "transparentFilledRectangle",
+		"visible": false,
+		"rect": {"x": 2, "y": 2, "w": 216, "h": 16},
+		"color": [0, 0, 0, 0],
+		"colorLine": [255, 255, 0, 255]
+	},
+	"dropDownTimers" : 
+	{
+		"name": "timerPresetSelector",
+		"type": "comboBox",
+		"image": "lobby/dropdown",
+		"imageOrder": [0, 0, 0, 0],
+		"items":
+		[
+			{
+				"name": "timer",
+				"type": "label",
+				"font": "small",
+				"alignment": "left",
+				"color": "white",
+				"text": "vcmi.optionsTab.turnTime.select"
+			}
+		],
+		"dropDown":
+		{
+			"items":
+			[
+				{
+					"type": "texture",
+					"image": "DiBoxBck",
+					"color" : "blue", 
+					"rect": {"x": 0, "y": 0, "w": 220, "h": 260}
+				},
+				{
+					"type": "transparentFilledRectangle",
+					"visible": false,
+					"rect": {"x": 0, "y": 0, "w": 220, "h": 260},
+					"color": [0, 0, 0, 0],
+					"colorLine": [64, 80, 128, 128]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 0},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.unlimited"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 20},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.classic.1"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 40},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.classic.2"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 60},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.classic.5"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 80},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.classic.10"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 100},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.classic.20"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 120},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.classic.30"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 140},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.chess.20"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 160},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.chess.16"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 180},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.chess.8"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 200},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.chess.4"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 220},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.chess.2"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 240},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.turnTime.chess.1"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				}
+			]
+		}
+	},
+	"dropDownSimturns" : 
+	{
+		"name": "timerPresetSelector",
+		"type": "comboBox",
+		"image": "lobby/dropdown",
+		"imageOrder": [0, 0, 0, 0],
+		"items":
+		[
+			{
+				"name": "timer",
+				"type": "label",
+				"font": "small",
+				"alignment": "left",
+				"color": "white",
+				"text": "vcmi.optionsTab.simturns.select"
+			}
+		],
+		"dropDown":
+		{
+			"items":
+			[
+				{
+					"type": "texture",
+					"image": "DiBoxBck",
+					"color" : "blue", 
+					"rect": {"x": 0, "y": 0, "w": 220, "h": 160}
+				},
+				{
+					"type": "transparentFilledRectangle",
+					"visible": false,
+					"rect": {"x": 0, "y": 0, "w": 220, "h": 160},
+					"color": [0, 0, 0, 0],
+					"colorLine": [64, 80, 128, 128]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 0},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.simturns.none"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 20},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.simturns.tillContactMax"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 40},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.simturns.tillContact1"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 60},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.simturns.tillContact2"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 80},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.simturns.tillContact4"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 100},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.simturns.blocked1"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 120},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.simturns.blocked2"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				},
+				{
+					"type": "item",
+					"position": {"x": 0, "y": 140},
+					"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+					"items":
+					[
+						{
+							"type": "dropDownBackground"
+						},
+						{
+							"type": "dropDownLabel",
+							"name": "labelName",
+							"text": "vcmi.optionsTab.simturns.blocked4"
+						},
+						{
+							"type": "dropDownHover",
+							"name": "hoverImage"
+						}
+					]
+				}
+			]
+		}
+	}
+}

+ 92 - 12
config/widgets/turnOptionsTab.json

@@ -1,4 +1,6 @@
 {
+	"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+
 	"customTypes" : {
 		"verticalLayout66" : {
 			"type" : "layout",
@@ -64,6 +66,28 @@
 			"rect": {"x": 60, "y": 48, "w": 320, "h": 0},
 			"adoptHeight": true
 		},
+		
+//		{
+//			"type": "label",
+//			"font": "medium",
+//			"alignment": "center",
+//			"color": "yellow",
+//			"text": "vcmi.optionsTab.selectPreset",
+//			"position": {"x": 105, "y": 100}
+//		},
+		{
+			"type" : "dropDownTimers",
+			"name": "timerPresetSelector",
+			"position": {"x": 160, "y": 78},
+			"dropDownPosition": {"x": 0, "y": 20}
+		},
+		{
+			"type" : "dropDownSimturns",
+			"name": "simturnsPresetSelector",
+			"position": {"x": 160, "y": 98},
+			"dropDownPosition": {"x": 0, "y": 20}
+		},
+		
 		{
 			"type": "texture",
 			"image": "DiBoxBck",
@@ -103,7 +127,7 @@
 		{
 			"type" : "verticalLayout66",
 			"customType" : "labelTitle",
-			"position": {"x": 70, "y": 134},
+			"position": {"x": 70, "y": 133},
 			"items":
 			[
 				{
@@ -116,10 +140,10 @@
 					"text": "vcmi.optionsTab.chessFieldBattle.hover"
 				},
 				{
-					"text": "vcmi.optionsTab.chessFieldCreature.hover"
+					"text": "vcmi.optionsTab.chessFieldUnit.hover"
 				},
 				{
-					"text": "vcmi.optionsTab.simturns"
+					"text": "vcmi.optionsTab.simturnsTitle"
 				}
 			]
 		},
@@ -134,16 +158,42 @@
 					"text": "vcmi.optionsTab.chessFieldBase.help"
 				},
 				{
-					"text": "vcmi.optionsTab.chessFieldTurn.help"
+					"name": "chessFieldTurnLabel"
 				},
 				{
 					"text": "vcmi.optionsTab.chessFieldBattle.help"
 				},
 				{
-					"text": "vcmi.optionsTab.chessFieldCreature.help"
+					"name": "chessFieldUnitLabel"
 				}
 			]
 		},
+		
+		{
+			"name": "buttonTurnTimerAccumulate",
+			"position": {"x": 160, "y": 195},
+			"type": "toggleButton",
+			"image": "lobby/checkbox",
+			"callback" : "setTurnTimerAccumulate"
+		},
+		{
+			"name": "buttonUnitTimerAccumulate",
+			"position": {"x": 160, "y": 327},
+			"type": "toggleButton",
+			"image": "lobby/checkbox",
+			"callback" : "setUnitTimerAccumulate"
+		},
+		
+		{
+			"type" : "labelTitle",
+			"position": {"x": 195, "y": 199},
+			"text" : "vcmi.optionsTab.accumulate"
+		},
+		{
+			"type" : "labelTitle",
+			"position": {"x": 195, "y": 331},
+			"text" : "vcmi.optionsTab.accumulate"
+		},
 
 		{
 			"type" : "verticalLayout66",
@@ -180,9 +230,9 @@
 					"help": "vcmi.optionsTab.chessFieldBattle.help"
 				},
 				{
-					"name": "chessFieldCreature",
-					"callback": "parseAndSetTimer_creature",
-					"help": "vcmi.optionsTab.chessFieldCreature.help"
+					"name": "chessFieldUnit",
+					"callback": "parseAndSetTimer_unit",
+					"help": "vcmi.optionsTab.chessFieldUnit.help"
 				}
 			]
 		},
@@ -249,7 +299,6 @@
 			"position": {"x": 278, "y": 478}
 		},
 		{
-			"type" : "label",
 			"text": "vcmi.optionsTab.simturnsMin.help",
 			"type": "multiLineLabel",
 			"font": "tiny",
@@ -258,7 +307,6 @@
 			"rect": {"x": 70, "y": 430, "w": 300, "h": 40}
 		},
 		{
-			"type" : "label",
 			"text": "vcmi.optionsTab.simturnsMax.help",
 			"type": "multiLineLabel",
 			"font": "tiny",
@@ -270,7 +318,8 @@
 			"name": "buttonSimturnsAI",
 			"position": {"x": 70, "y": 535},
 			"type": "toggleButton",
-			"image": "lobby/checkbox"
+			"image": "lobby/checkbox",
+			"callback" : "setSimturnAI"
 		},
 		{
 			"name": "labelSimturnsAI",
@@ -281,5 +330,36 @@
 			"text": "vcmi.optionsTab.simturnsAI.hover",
 			"position": {"x": 110, "y": 540}
 		}
-	]
+	],
+	
+	"variables":
+	{
+		"timerPresets" :
+		[
+			[    0,    0,   0, 0, false, false],
+			[    0,   60,   0, 0, false, false],
+			[    0,  120,   0, 0, false, false],
+			[    0,  300,   0, 0, false, false],
+			[    0,  600,   0, 0, false, false],
+			[    0, 1200,   0, 0, false, false],
+			[    0, 1800,   0, 0, false, false],
+			[ 1200,  600, 120, 0, true, false],
+			[  960,  480,  90, 0, true, false],
+			[  480,  240,  60, 0, true, false],
+			[  240,  120,  30, 0, true, false],
+			[  120,   60,  15, 0, true, false],
+			[   60,   60,   0, 0, true, false]
+		],
+		"simturnsPresets" :
+		[
+			[   0,  0, false],
+			[ 999,  0, false],
+			[   7,  0, false],
+			[  14,  0, false],
+			[  28,  0, false],
+			[   7,  7, false],
+			[  14, 14, false],
+			[  28, 28, false]
+		]
+	}
 }

+ 7 - 0
lib/StartInfo.h

@@ -32,6 +32,13 @@ struct DLL_LINKAGE SimturnsInfo
 	/// If set to true, human and 1 AI can act at the same time
 	bool allowHumanWithAI = false;
 
+	bool operator == (const SimturnsInfo & other) const
+	{
+		return requiredTurns == other.requiredTurns &&
+				optionalTurns == other.optionalTurns &&
+				allowHumanWithAI == other.allowHumanWithAI;
+	}
+
 	template <typename Handler>
 	void serialize(Handler &h, const int version)
 	{

+ 1 - 1
lib/TurnTimerInfo.cpp

@@ -19,7 +19,7 @@ bool TurnTimerInfo::isEnabled() const
 
 bool TurnTimerInfo::isBattleEnabled() const
 {
-	return creatureTimer > 0 || battleTimer > 0;
+	return turnTimer > 0 || baseTimer > 0 || unitTimer > 0 || battleTimer > 0;
 }
 
 VCMI_LIB_NAMESPACE_END

+ 17 - 2
lib/TurnTimerInfo.h

@@ -17,13 +17,26 @@ struct DLL_LINKAGE TurnTimerInfo
 	int turnTimer = 0; //in ms, counts down when player is making his turn on adventure map
 	int baseTimer = 0; //in ms, counts down only when turn timer runs out
 	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
+	int unitTimer = 0; //in ms, counts down when player is choosing action in battle
+
+	bool accumulatingTurnTimer = false;
+	bool accumulatingUnitTimer = false;
 	
 	bool isActive = false; //is being counting down
 	bool isBattle = false; //indicator for current timer mode
 	
 	bool isEnabled() const;
 	bool isBattleEnabled() const;
+
+	bool operator == (const TurnTimerInfo & other) const
+	{
+		return turnTimer == other.turnTimer &&
+				baseTimer == other.baseTimer &&
+				battleTimer == other.battleTimer &&
+				unitTimer == other.unitTimer &&
+				accumulatingTurnTimer == other.accumulatingTurnTimer &&
+				accumulatingUnitTimer == other.accumulatingUnitTimer;
+	}
 	
 	template <typename Handler>
 	void serialize(Handler &h, const int version)
@@ -31,7 +44,9 @@ struct DLL_LINKAGE TurnTimerInfo
 		h & turnTimer;
 		h & baseTimer;
 		h & battleTimer;
-		h & creatureTimer;
+		h & unitTimer;
+		h & accumulatingTurnTimer;
+		h & accumulatingUnitTimer;
 		h & isActive;
 		h & isBattle;
 	}

+ 39 - 64
server/TurnTimerHandler.cpp

@@ -74,7 +74,7 @@ void TurnTimerHandler::onPlayerGetTurn(PlayerColor player)
 		{
 			endTurnAllowed[player] = true;
 			auto & timer = timers[player];
-			if(si->turnTimerInfo.baseTimer > 0)
+			if(si->turnTimerInfo.accumulatingTurnTimer)
 				timer.baseTimer += timer.turnTimer;
 			timer.turnTimer = si->turnTimerInfo.turnTimer;
 			
@@ -127,17 +127,14 @@ void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime)
 	const auto * state = gameHandler.getPlayerState(player);
 	if(state && state->human && timer.isActive && !timer.isBattle && state->status == EPlayerStatus::INGAME)
 	{
-		if(!timerCountDown(timer.turnTimer, si->turnTimerInfo.turnTimer, player, waitTime))
-		{
-			if(timer.baseTimer > 0)
-			{
-				timer.turnTimer = timer.baseTimer;
-				timer.baseTimer = 0;
-				onPlayerMakingTurn(player, 0);
-			}
-			else if(endTurnAllowed[state->color] && !gameHandler.queries->topQuery(state->color)) //wait for replies to avoid pending queries
-				gameHandler.turnOrder->onPlayerEndsTurn(state->color);
-		}
+		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);
 	}
 }
 
@@ -176,8 +173,8 @@ void TurnTimerHandler::onBattleStart(const BattleID & battleID)
 			auto & timer = timers[i];
 			timer.isBattle = true;
 			timer.isActive = si->turnTimerInfo.isBattleEnabled();
-			timer.battleTimer = (pvpBattle ? si->turnTimerInfo.battleTimer : 0);
-			timer.creatureTimer = (pvpBattle ? si->turnTimerInfo.creatureTimer : si->turnTimerInfo.battleTimer);
+			timer.battleTimer = si->turnTimerInfo.battleTimer;
+			timer.unitTimer = (pvpBattle ? si->turnTimerInfo.unitTimer : 0);
 			
 			sendTimerUpdate(i);
 		}
@@ -201,8 +198,6 @@ void TurnTimerHandler::onBattleEnd(const BattleID & battleID)
 	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())
@@ -210,14 +205,6 @@ void TurnTimerHandler::onBattleEnd(const BattleID & battleID)
 			auto & timer = timers[i];
 			timer.isBattle = false;
 			timer.isActive = true;
-			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);
 		}
 	}
@@ -242,9 +229,9 @@ void TurnTimerHandler::onBattleNextStack(const BattleID & battleID, const CStack
 		auto player = stack.getOwner();
 		
 		auto & timer = timers[player];
-		if(timer.battleTimer == 0)
-			timer.battleTimer = timer.creatureTimer;
-		timer.creatureTimer = si->turnTimerInfo.creatureTimer;
+		if(timer.accumulatingUnitTimer)
+			timer.battleTimer += timer.unitTimer;
+		timer.unitTimer = si->turnTimerInfo.unitTimer;
 		
 		sendTimerUpdate(player);
 	}
@@ -283,56 +270,44 @@ void TurnTimerHandler::onBattleLoop(const BattleID & battleID, int waitTime)
 		return;
 	
 	const auto * state = gameHandler.getPlayerState(player);
-	assert(state && state->status != EPlayerStatus::INGAME);
+	assert(state && state->status == EPlayerStatus::INGAME);
 	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(timer.isActive && timer.isBattle)
 	{
+		 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))
 		{
-			if(timer.battleTimer > 0)
-			{
-				timer.creatureTimer = timer.battleTimer;
-				timerCountDown(timer.creatureTimer, timer.battleTimer, player, 0);
-				timer.battleTimer = 0;
-			}
+			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(battleID, player, doNothing);
+				doNothing.actionType = EActionType::DEFEND;
+				doNothing.stackNumber = stack->unitId();
 			}
+			gameHandler.battles->makePlayerBattleAction(battleID, 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(battleID, player, retreat);
-			}
+			BattleAction retreat;
+			retreat.side = side;
+			retreat.actionType = EActionType::RETREAT; //harsh punishment
+			gameHandler.battles->makePlayerBattleAction(battleID, player, retreat);
 		}
 	}
 }