فهرست منبع

Merge pull request #3391 from vcmi/master

Merge master -> develop
Ivan Savenko 1 سال پیش
والد
کامیت
bb6179d05e
100فایلهای تغییر یافته به همراه2953 افزوده شده و 742 حذف شده
  1. 11 1
      .github/workflows/github.yml
  2. 23 19
      AI/BattleAI/BattleEvaluator.cpp
  3. 1 0
      CI/conan/base/cross-macro.j2
  4. 16 0
      CI/mingw-32/before_install.sh
  5. 1 1
      CI/mingw/before_install.sh
  6. 4 1
      CMakeLists.txt
  7. 80 0
      ChangeLog.md
  8. 101 9
      Mods/vcmi/config/vcmi/chinese.json
  9. 267 160
      Mods/vcmi/config/vcmi/czech.json
  10. 5 0
      Mods/vcmi/config/vcmi/english.json
  11. 2 0
      Mods/vcmi/config/vcmi/german.json
  12. 5 0
      Mods/vcmi/config/vcmi/ukrainian.json
  13. 2 2
      android/vcmi-app/build.gradle
  14. 0 8
      android/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java
  15. 71 0
      android/vcmi-app/src/main/res/values-cs/strings.xml
  16. 31 11
      client/CMT.cpp
  17. 1 1
      client/CVideoHandler.cpp
  18. 5 1
      client/NetPacksLobbyClient.cpp
  19. 8 6
      client/PlayerLocalState.cpp
  20. 4 4
      client/PlayerLocalState.h
  21. 7 2
      client/adventureMap/AdventureMapInterface.cpp
  22. 3 0
      client/adventureMap/AdventureMapInterface.h
  23. 3 0
      client/adventureMap/CInGameConsole.cpp
  24. 31 31
      client/adventureMap/CList.cpp
  25. 1 3
      client/adventureMap/CList.h
  26. 140 111
      client/adventureMap/TurnTimerWidget.cpp
  27. 23 30
      client/adventureMap/TurnTimerWidget.h
  28. 3 3
      client/battle/BattleActionsController.cpp
  29. 4 1
      client/battle/BattleInterface.cpp
  30. 1 0
      client/battle/BattleInterface.h
  31. 22 2
      client/battle/BattleInterfaceClasses.cpp
  32. 4 2
      client/battle/BattleInterfaceClasses.h
  33. 2 0
      client/battle/BattleStacksController.cpp
  34. 43 4
      client/battle/BattleWindow.cpp
  35. 5 0
      client/battle/BattleWindow.h
  36. 2 4
      client/lobby/CBonusSelection.cpp
  37. 2 0
      client/lobby/CLobbyScreen.cpp
  38. 6 3
      client/lobby/CSelectionBase.cpp
  39. 2 0
      client/lobby/CSelectionBase.h
  40. 1 1
      client/lobby/OptionsTab.cpp
  41. 5 1
      client/lobby/RandomMapTab.cpp
  42. 2 3
      client/mainmenu/CHighScoreScreen.cpp
  43. 20 3
      client/mainmenu/CMainMenu.cpp
  44. 3 0
      client/mapView/MapViewController.cpp
  45. 30 6
      client/widgets/CArtifactHolder.cpp
  46. 14 9
      client/widgets/CArtifactHolder.h
  47. 3 3
      client/widgets/CArtifactsOfHeroAltar.cpp
  48. 1 1
      client/widgets/CArtifactsOfHeroAltar.h
  49. 148 40
      client/widgets/CArtifactsOfHeroBackpack.cpp
  50. 24 6
      client/widgets/CArtifactsOfHeroBackpack.h
  51. 26 11
      client/widgets/CArtifactsOfHeroBase.cpp
  52. 7 4
      client/widgets/CArtifactsOfHeroBase.h
  53. 6 5
      client/widgets/CArtifactsOfHeroKingdom.cpp
  54. 1 1
      client/widgets/CArtifactsOfHeroKingdom.h
  55. 4 3
      client/widgets/CArtifactsOfHeroMain.cpp
  56. 1 1
      client/widgets/CArtifactsOfHeroMain.h
  57. 2 2
      client/widgets/CArtifactsOfHeroMarket.cpp
  58. 1 1
      client/widgets/CArtifactsOfHeroMarket.h
  59. 46 12
      client/widgets/CWindowWithArtifacts.cpp
  60. 5 3
      client/widgets/CWindowWithArtifacts.h
  61. 14 2
      client/widgets/MiscWidgets.cpp
  62. 3 0
      client/widgets/MiscWidgets.h
  63. 6 3
      client/widgets/TextControls.cpp
  64. 51 14
      client/windows/CHeroBackpackWindow.cpp
  65. 17 2
      client/windows/CHeroBackpackWindow.h
  66. 0 1
      client/windows/CHeroWindow.h
  67. 3 3
      client/windows/CMapOverview.cpp
  68. 114 62
      client/windows/CSpellWindow.cpp
  69. 8 0
      client/windows/CSpellWindow.h
  70. 1 1
      client/windows/CTradeWindow.cpp
  71. 1 1
      client/windows/CTradeWindow.h
  72. 3 1
      client/windows/GUIClasses.cpp
  73. 2 1
      client/windows/GUIClasses.h
  74. 7 0
      client/windows/settings/AdventureOptionsTab.cpp
  75. 11 1
      client/windows/settings/GeneralOptionsTab.cpp
  76. 1 1
      cmake_modules/VersionDefinition.cmake
  77. 2 0
      config/artifacts.json
  78. 7 2
      config/schemas/settings.json
  79. 5 2
      config/spells/timed.json
  80. 2 1
      config/terrainViewPatterns.json
  81. 14 6
      config/widgets/settings/adventureOptionsTab.json
  82. 29 0
      config/widgets/settings/generalOptionsTab.json
  83. 0 34
      config/widgets/turnTimer.json
  84. 6 0
      debian/changelog
  85. 1 0
      docs/Readme.md
  86. 2 1
      docs/modders/Bonus/Bonus_Duration_Types.md
  87. 15 3
      docs/players/Cheat_Codes.md
  88. 1 1
      include/vstd/DateUtils.h
  89. 61 37
      launcher/eu.vcmi.VCMI.metainfo.xml
  90. 7 7
      launcher/mainwindow_moc.cpp
  91. 9 1
      launcher/modManager/cmodlistview_moc.cpp
  92. 1 0
      launcher/modManager/cmodlistview_moc.h
  93. 16 5
      launcher/modManager/cmodmanager.cpp
  94. 1207 0
      launcher/translation/czech.ts
  95. 20 6
      lib/BasicTypes.cpp
  96. 5 3
      lib/CArtifactInstance.cpp
  97. 1 0
      lib/CArtifactInstance.h
  98. 5 0
      lib/CGameInfoCallback.cpp
  99. 1 1
      lib/CGameInfoCallback.h
  100. 2 3
      lib/GameSettings.cpp

+ 11 - 1
.github/workflows/github.yml

@@ -116,7 +116,7 @@ jobs:
             pack_type: RelWithDebInfo
             extension: exe
             preset: windows-msvc-release-ccache
-          - platform: mingw-ubuntu
+          - platform: mingw
             os: ubuntu-22.04
             test: 0
             pack: 1
@@ -126,6 +126,16 @@ jobs:
             cmake_args: -G Ninja
             preset: windows-mingw-conan-linux
             conan_profile: mingw64-linux.jinja
+          - platform: mingw-32
+            os: ubuntu-22.04
+            test: 0
+            pack: 1
+            pack_type: Release
+            extension: exe
+            cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
+            cmake_args: -G Ninja
+            preset: windows-mingw-conan-linux
+            conan_profile: mingw32-linux.jinja
           - platform: android-32
             os: ubuntu-22.04
             extension: apk

+ 23 - 19
AI/BattleAI/BattleEvaluator.cpp

@@ -70,8 +70,9 @@ std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
 std::optional<PossibleSpellcast> BattleEvaluator::findBestCreatureSpell(const CStack *stack)
 {
 	//TODO: faerie dragon type spell should be selected by server
-	SpellID creatureSpellToCast = cb->getBattle(battleID)->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
-	if(stack->hasBonusOfType(BonusType::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
+	SpellID creatureSpellToCast = cb->getBattle(battleID)->getRandomCastedSpell(CRandomGenerator::getDefault(), stack);
+
+	if(stack->canCast() && creatureSpellToCast != SpellID::NONE)
 	{
 		const CSpell * spell = creatureSpellToCast.toSpell();
 
@@ -427,33 +428,36 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 
 				state->nextTurn(unit->unitId());
 
-				PotentialTargets pt(unit, damageCache, state);
+				PotentialTargets potentialTargets(unit, damageCache, state);
 
-				if(!pt.possibleAttacks.empty())
+				if(!potentialTargets.possibleAttacks.empty())
 				{
-					AttackPossibility ap = pt.bestAction();
+					AttackPossibility attackPossibility = potentialTargets.bestAction();
 
-					auto swb = state->getForUpdate(unit->unitId());
-					*swb = *ap.attackerState;
+					auto stackWithBonuses = state->getForUpdate(unit->unitId());
+					*stackWithBonuses = *attackPossibility.attackerState;
 
-					if(ap.defenderDamageReduce > 0)
-						swb->removeUnitBonus(Bonus::UntilAttack);
-					if(ap.attackerDamageReduce > 0)
-						swb->removeUnitBonus(Bonus::UntilBeingAttacked);
+					if(attackPossibility.defenderDamageReduce > 0)
+					{
+						stackWithBonuses->removeUnitBonus(Bonus::UntilAttack);
+						stackWithBonuses->removeUnitBonus(Bonus::UntilOwnAttack);
+					}
+					if(attackPossibility.attackerDamageReduce > 0)
+						stackWithBonuses->removeUnitBonus(Bonus::UntilBeingAttacked);
 
-					for(auto affected : ap.affectedUnits)
+					for(auto affected : attackPossibility.affectedUnits)
 					{
-						swb = state->getForUpdate(affected->unitId());
-						*swb = *affected;
+						stackWithBonuses = state->getForUpdate(affected->unitId());
+						*stackWithBonuses = *affected;
 
-						if(ap.defenderDamageReduce > 0)
-							swb->removeUnitBonus(Bonus::UntilBeingAttacked);
-						if(ap.attackerDamageReduce > 0 && ap.attack.defender->unitId() == affected->unitId())
-							swb->removeUnitBonus(Bonus::UntilAttack);
+						if(attackPossibility.defenderDamageReduce > 0)
+							stackWithBonuses->removeUnitBonus(Bonus::UntilBeingAttacked);
+						if(attackPossibility.attackerDamageReduce > 0 && attackPossibility.attack.defender->unitId() == affected->unitId())
+							stackWithBonuses->removeUnitBonus(Bonus::UntilAttack);
 					}
 				}
 
-				auto bav = pt.bestActionValue();
+				auto bav = potentialTargets.bestActionValue();
 
 				//best action is from effective owner`s point if view, we need to convert to our point if view
 				if(state->battleGetOwner(unit) != playerID)

+ 1 - 0
CI/conan/base/cross-macro.j2

@@ -17,4 +17,5 @@ RC={{ target_host }}-windres
 {% macro generate_conf(target_host) -%}
 tools.build:compiler_executables = {"c": "{{ target_host }}-gcc", "cpp": "{{ target_host }}-g++"}
 tools.build:sysroot = /usr/{{ target_host }}
+tools.build:defines = ["WINVER=0x0601", "_WIN32_WINNT=0x0601"]
 {%- endmacro -%}

+ 16 - 0
CI/mingw-32/before_install.sh

@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+sudo apt-get update
+sudo apt-get install ninja-build mingw-w64 nsis
+sudo update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix
+
+# Workaround for getting new MinGW headers on Ubuntu 22.04.
+# Remove it once MinGW headers version in repository will be 10.0 at least
+curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-common_10.0.0-3_all.deb \
+  && sudo dpkg -i mingw-w64-common_10.0.0-3_all.deb;
+curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-i686-dev_10.0.0-3_all.deb \
+  && sudo dpkg -i mingw-w64-i686-dev_10.0.0-3_all.deb;
+
+mkdir ~/.conan ; cd ~/.conan
+curl -L "https://github.com/vcmi/vcmi-deps-windows-conan/releases/download/1.1/vcmi-deps-windows-conan-w32.tgz" \
+	| tar -xzf -

+ 1 - 1
CI/mingw-ubuntu/before_install.sh → CI/mingw/before_install.sh

@@ -12,5 +12,5 @@ curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-
   && sudo dpkg -i mingw-w64-x86-64-dev_10.0.0-3_all.deb;
 
 mkdir ~/.conan ; cd ~/.conan
-curl -L "https://github.com/vcmi/vcmi-deps-windows-conan/releases/download/1.0/vcmi-deps-windows-conan-w64.tgz" \
+curl -L "https://github.com/vcmi/vcmi-deps-windows-conan/releases/download/1.1/vcmi-deps-windows-conan-w64.tgz" \
 	| tar -xzf -

+ 4 - 1
CMakeLists.txt

@@ -255,7 +255,10 @@ endif()
 
 if(MINGW OR MSVC)
 	# Windows Vista or newer for FuzzyLite 6 to compile
-	add_definitions(-D_WIN32_WINNT=0x0600)
+	# Except for conan which already has this definition in its preset
+	if(NOT USING_CONAN)
+		add_definitions(-D_WIN32_WINNT=0x0600)
+	endif()
 
 	#delete lib prefix for dlls (libvcmi -> vcmi)
 	set(CMAKE_SHARED_LIBRARY_PREFIX "")

+ 80 - 0
ChangeLog.md

@@ -1,3 +1,83 @@
+# 1.4.1 -> 1.4.2
+
+### General
+* Restored support for Windows 7
+* Restored support for 32-bit builds
+* Implemented quick backpack window for slot-specific artifact selection, activated via mouse wheel / swipe gesture
+* Added option to search for specific spell in the spellbook
+* Added option to skip fading animation on adventure map
+* Using alt-tab to switch to another application will no longer activate in-game console/chat
+* Increased frequency of checks for server startup to improve server connection time
+* added nwcfollowthewhiterabbit / vcmiluck cheat: the currently selected hero permanently gains maximum luck.
+* added nwcmorpheus / vcmimorale cheat: the currently selected hero permanently gains maximum morale.
+* added nwcoracle / vcmiobelisk cheat: the puzzle map is permanently revealed.
+* added nwctheone / vcmigod cheat: reveals the whole map, gives 5 archangels in each empty slot, unlimited movement points and permanent flight to currently selected hero
+
+### Launcher
+* Launcher will now properly show mod installation progress
+* Launcher will now correctly select preferred language on first start
+
+### Multiplayer
+* Timers for all players will now be visible at once
+* Turn options menu will correctly open for guests when host switches to it
+* Guests will correctly see which roads are allowed for random maps by host
+* Game will now correctly deactivate unit when timer runs out in pvp battle
+* Game will show turn, battle and unit timers separately during battles
+* Timer in pvp battles will be only active if unit timer is non-zero
+* Timer during adventure map turn will be active only if turn timer is non-zero
+* Game will now send notifications to players when simultaneous turns end
+
+### Stability
+* Fixed crash on clicking town or hero list on MacOS and iOS
+* Fixed crash on closing vcmi on Android
+* Fixed crash on disconnection from multiplayer game
+* Fixed crash on finishing game on last day of the month
+* Fixed crash on loading h3m maps with mods that alter Witch Hut, Shrine or Scholar
+* Fixed crash on opening creature morale detalisation in some localizations
+* Fixed possible crash on starting a battle when opening sound from previous battle is still playing
+* Fixed crash on map loading in case if there is no suitable option for a random dwelling
+* Fixed crash on usage of radial wheel to reorder towns or heroes
+* Fixed possible crash on random map generation
+* Fixed crash on attempting to transfer last creature when stack experience is enabled
+* Fixed crash on accessing invalid settings options
+* Fixed server crash on receiving invalid message from player
+* Added check for presence of Armageddon Blade campaign files to avoid crash on some Heroes 3 versions
+
+### Random Maps Generator
+* Improved performance of random maps generation
+* Rebalance of treasure values and density
+* Improve junction zones generation by spacing Monoliths
+* Reduced amount of terrain decorations to level more in line with H3
+* Generator will now avoid path routing near map border
+* Generator will now check full object area for minimum distance requirement
+* Fixed routing of roads behind Subterranean Gates, Monoliths and Mines
+* Fixed remaining issues with placement of Corpse
+* Fixed placement of one-tile prisons from HotA
+* Fixed spawning of Armageddon's Blade and Vial of Dragon Blood on random maps
+
+### Interface
+* Right-clicking hero icon during levelup dialog will now show hero status window
+* Added indicator of current turn to unit turn order panel in battles
+* Reduces upscaling artifacts on large spellbook
+* Game will now display correct date of saved games on Android
+* Fixed black screen appearing during spellbook page flip animation 
+* Fixed description of "Start map with hero" bonus in campaigns
+* Fixed invisible chat text input in game lobby
+* Fixed positioning of chat history in game lobby
+* "Infobar Creature Management" option is now enabled by default
+* "Large Spellbook" option is now enabled by default
+
+### Mechanics
+* Anti-magic garrison now actually blocks spell casting
+* Berserk spell will no longer cancel if affected unit performs counterattack
+* Frenzy spell can no longer be casted on units that should be immune to it
+* Master Genie will no longer attempt to cast beneficial spell on creatures immune to it
+* Vitality and damage skills of a commander will now correctly grow with level
+
+### Modding
+* Added UNTIL_OWN_ATTACK duration type for bonuses
+* Configurable objects with visit mode "first" and "random" now respect "canRefuse" flag
+
 # 1.4.0 -> 1.4.1
 
 ### General

+ 101 - 9
Mods/vcmi/config/vcmi/chinese.json

@@ -36,14 +36,27 @@
 	"vcmi.heroOverview.spells" : "魔法",
 
 	"vcmi.radialWheel.mergeSameUnit" : "合并相同生物",
-	"vcmi.radialWheel.showUnitInformation" : "显示生物信息",
+	"vcmi.radialWheel.fillSingleUnit" : "单个生物填充空格",
 	"vcmi.radialWheel.splitSingleUnit" : "分割单个生物",
 	"vcmi.radialWheel.splitUnitEqually" : "平均分配生物",
 	"vcmi.radialWheel.moveUnit" : "将生物移动到部队",
 	"vcmi.radialWheel.splitUnit" : "分割生物到其他空位",
 
+	"vcmi.radialWheel.heroGetArmy" : "移动生物",
+	"vcmi.radialWheel.heroSwapArmy" : "交换生物",
+	"vcmi.radialWheel.heroExchange" : "开启英雄交换",
+	"vcmi.radialWheel.heroGetArtifacts" : "移动宝物",
+	"vcmi.radialWheel.heroSwapArtifacts" : "交换宝物",
+	"vcmi.radialWheel.heroDismiss" : "解雇英雄",
+
+	"vcmi.radialWheel.moveTop" : "移到顶端",
+	"vcmi.radialWheel.moveUp" : "上移",
+	"vcmi.radialWheel.moveDown" : "下移",
+	"vcmi.radialWheel.moveBottom" : "移到底端",
+	
 	"vcmi.mainMenu.serverConnecting" : "连接中...",
 	"vcmi.mainMenu.serverAddressEnter" : "使用地址:",
+	"vcmi.mainMenu.serverConnectionFailed" : "连接失败",
 	"vcmi.mainMenu.serverClosing" : "关闭中...",
 	"vcmi.mainMenu.hostTCP" : "创建TCP/IP游戏",
 	"vcmi.mainMenu.joinTCP" : "加入TCP/IP游戏",
@@ -51,11 +64,18 @@
 
 	"vcmi.lobby.filename" : "文件名",
 	"vcmi.lobby.creationDate" : "创建时间",
+	"vcmi.lobby.scenarioName" : "场景名称",
+	"vcmi.lobby.mapPreview" : "地图预览",
+	"vcmi.lobby.noPreview" : "无地上部分",
+	"vcmi.lobby.noUnderground" : "无地下部分",
 
 	"vcmi.server.errors.existingProcess"     : "一个VCMI进程已经在运行,启动新进程前请结束它。",
 	"vcmi.server.errors.modsToEnable"    : "{需要启用的mod列表}",
 	"vcmi.server.errors.modsToDisable"   : "{需要禁用的mod列表}",
 	"vcmi.server.confirmReconnect"           : "您想要重连上一个会话么?",
+	"vcmi.server.errors.modNoDependency" : "读取mod包 {'%s'}失败!\n 需要的mod {'%s'} 没有安装或无效!\n",
+	"vcmi.server.errors.modConflict" : "读取的mod包 {'%s'}无法运行!\n 与另一个mod {'%s'}冲突!\n",
+	"vcmi.server.errors.unknownEntity" : "加载保存失败! 在保存的游戏中发现未知实体'%s'! 保存可能与当前安装的mod版本不兼容!",
 
 	"vcmi.settingsMainWindow.generalTab.hover"   : "常规",
 	"vcmi.settingsMainWindow.generalTab.help"    : "切换到“常规”选项卡 - 设置游戏客户端呈现",
@@ -92,6 +112,10 @@
 	"vcmi.systemOptions.hapticFeedbackButton.help"   : "{触觉反馈}\n\n切换触摸输入的触觉反馈。",
 	"vcmi.systemOptions.enableUiEnhancementsButton.hover"  : "界面增强",
 	"vcmi.systemOptions.enableUiEnhancementsButton.help"   : "{界面增强}\n\n显示所有界面增强内容,如大背包和魔法书等。",
+	"vcmi.systemOptions.enableLargeSpellbookButton.hover"  : "增大魔法书界面",
+	"vcmi.systemOptions.enableLargeSpellbookButton.help"   : "{增大魔法书界面}\n\n可以在魔法书单页中显示更多的魔法,从而获得更好的视觉效果。",
+	"vcmi.systemOptions.audioMuteFocus.hover"  : "切换窗口时静音",
+	"vcmi.systemOptions.audioMuteFocus.help"   : "{切换窗口时静音}\n\n快速切换窗口时将静音,在工作时,切换游戏窗口不会有声音。",
 
 	"vcmi.adventureOptions.infoBarPick.hover" : "在信息面板显示消息",
 	"vcmi.adventureOptions.infoBarPick.help" : "{在信息面板显示消息}\n\n来自访问地图物件的信息将显示在信息面板,而不是弹出窗口。",
@@ -107,6 +131,8 @@
 	"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{信息面板生物管理}\n\n允许在信息面板中重新排列生物,而不是在默认组件之间循环。",
 	"vcmi.adventureOptions.leftButtonDrag.hover" : "左键拖动地图",
 	"vcmi.adventureOptions.leftButtonDrag.help" : "{左键拖动地图}\n\n启用后,按住左键移动鼠标将拖动冒险地图视图。",
+	"vcmi.adventureOptions.smoothDragging.hover" : "平滑地图拖动",
+	"vcmi.adventureOptions.smoothDragging.help" : "{平滑地图拖动}\n\n启用后,地图拖动会产生柔和的羽化效果。",
 	"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
@@ -139,6 +165,9 @@
 	"vcmi.battleOptions.enableAutocombatSpells.help": "{魔法}\n\n快速战斗时不会使用魔法。",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "跳过战斗开始音乐",
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过战斗开始音乐}\n\n战斗开始音乐播放期间,你也能够进行操作。",
+	
+	"vcmi.adventureMap.revisitObject.hover" : "重新访问",
+	"vcmi.adventureMap.revisitObject.help" : "{重新访问}\n\n让当前英雄重新访问地图建筑或城镇。",
 
 	"vcmi.battleWindow.pressKeyToSkipIntro" : "按下任意键立即开始战斗",
 	"vcmi.battleWindow.damageEstimation.melee" : "近战攻击 %CREATURE (%DAMAGE).",
@@ -153,6 +182,15 @@
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d 将被消灭",
 
 	"vcmi.battleResultsWindow.applyResultsLabel" : "接受战斗结果",
+	
+	"vcmi.tutorialWindow.title" : "触摸屏介绍",
+	"vcmi.tutorialWindow.decription.RightClick" : "触摸并按住要右键单击的元素。 触摸可用区域以关闭。",
+	"vcmi.tutorialWindow.decription.MapPanning" : "用一根手指触摸并拖动来移动地图。",
+	"vcmi.tutorialWindow.decription.MapZooming" : "用两根手指捏合可更改地图缩放比例。",
+	"vcmi.tutorialWindow.decription.RadialWheel" : "滑动可打开径向轮以执行各种操作,例如生物/英雄管理和城镇排序。",
+	"vcmi.tutorialWindow.decription.BattleDirection" : "要从特定方向攻击,请向要进行攻击的方向滑动。",
+	"vcmi.tutorialWindow.decription.BattleDirectionAbort" : "如果手指距离足够远,可以取消攻击方向手势。",
+	"vcmi.tutorialWindow.decription.AbortSpell" : "触摸并按住可取消魔法。",
 
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "显示可招募生物",
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{显示可招募生物}\n\n在城镇摘要(城镇屏幕的左下角)中显示可招募的生物数量,而不是增长。",
@@ -202,14 +240,68 @@
 	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "同盟关系",
 	"vcmi.randomMapTab.widgets.roadTypesLabel"       : "道路类型",
 
-	"vcmi.optionsTab.chessFieldBase.hover" : "额外计时器",
-	"vcmi.optionsTab.chessFieldTurn.hover" : "转动计时器",
+	"vcmi.optionsTab.turnOptions.hover" : "回合选项",
+	"vcmi.optionsTab.turnOptions.help" : "选择回合计时器并同步回合选项",
+	"vcmi.optionsTab.selectPreset" : "预设",
+
+	"vcmi.optionsTab.chessFieldBase.hover" : "基本计时器",
+	"vcmi.optionsTab.chessFieldTurn.hover" : "回合计时器",
 	"vcmi.optionsTab.chessFieldBattle.hover" : "战斗计时器",
-	"vcmi.optionsTab.chessFieldUnit.hover" : "堆栈计时器",
-	"vcmi.optionsTab.chessFieldBase.help" : "当{转动计时器}达到零时开始倒计时。 它仅在游戏开始时设置一次。 当计时器达到零时,玩家的回合结束。",
-	"vcmi.optionsTab.chessFieldTurn.help" : "当玩家在冒险地图上开始回合时开始倒计时。 它在每回合开始时重置为其初始值。 任何未使用的回合时间将被添加到{额外计时器}(如果正在使用)中。",
-	"vcmi.optionsTab.chessFieldBattle.help" : "战斗期间当 {堆栈计时器} 达到0时进行倒计时。 每次战斗开始时都会重置为初始值。 如果计时器达到零,当前活动的堆栈将进行防御。",
-	"vcmi.optionsTab.chessFieldUnit.help" : "当玩家在战斗中为当前堆栈选择一个动作时开始倒计时。 堆栈操作完成后,它会重置为其初始值。",
+	"vcmi.optionsTab.chessFieldUnit.hover" : "单位计时器",
+	"vcmi.optionsTab.chessFieldBase.help" : "当 {回合计时器} 达到 0 时使用。在游戏开始时设置一次。达到 0 时,结束当前回合。任何正在进行的战斗都会以失败告终。",
+	"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "在战斗外或{战斗计时器}耗尽时使用。 每回合重置。 剩余部分在回合结束时添加至 {基本计时器}。",
+	"vcmi.optionsTab.chessFieldTurnDiscard.help" : "在战斗外或{战斗计时器}耗尽时使用。 每回合重置。 任何未花费的时间都会丢失。",
+	"vcmi.optionsTab.chessFieldBattle.help" : "在与 AI 的战斗中使用,或者在 {单位计时器} 耗尽时用于 pvp 战斗。在每次战斗开始时重置。",
+	"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "在 PVP 战斗中选择单位动作时使用。 在单位回合结束时将剩余物添加到{战斗计时器}。",
+	"vcmi.optionsTab.chessFieldUnitDiscard.help" : "在 PVP 战斗中选择单位动作时使用。 在每个单位回合开始时重置。 任何未花费的时间都会丢失。",
+	
+	"vcmi.optionsTab.accumulate" : "累积",
+
+	"vcmi.optionsTab.simturnsTitle" : "同时进行回合",
+	"vcmi.optionsTab.simturnsMin.hover" : "最少回合",
+	"vcmi.optionsTab.simturnsMax.hover" : "最多回合",
+	"vcmi.optionsTab.simturnsAI.hover" : "(测试中) AI回合同时行动",
+	"vcmi.optionsTab.simturnsMin.help" : "同时游戏进行的最少指定天数。在此期间玩家之间的联系将被阻止",
+	"vcmi.optionsTab.simturnsMax.help" : "同时游戏指定的最多天数或直到与其他玩家联系",
+	"vcmi.optionsTab.simturnsAI.help" : "{AI回合同时行动}\n实验选项。启用同时回合后,允许 AI 玩家与人类玩家同时行动。",
+	
+	"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 天",
+	"vcmi.optionsTab.simturns.days.1" : " %d 天",
+	"vcmi.optionsTab.simturns.days.2" : " %d 天",
+	"vcmi.optionsTab.simturns.weeks.0" : " %d 周",
+	"vcmi.optionsTab.simturns.weeks.1" : " %d 周",
+	"vcmi.optionsTab.simturns.weeks.2" : " %d 周",
+	"vcmi.optionsTab.simturns.months.0" : " %d 月",
+	"vcmi.optionsTab.simturns.months.1" : " %d 月",
+	"vcmi.optionsTab.simturns.months.2" : " %d 月",
+
 
 	// Custom victory conditions for H3 campaigns and HotA maps
 	"vcmi.map.victoryCondition.daysPassed.toOthers" : "敌人依然存活至今,你失败了!",
@@ -275,7 +367,7 @@
 	"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "忽略防御 (${val}%)",
 	"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "当攻击时,目标生物${val}%的防御力将被无视。",
 	"core.bonus.FIRE_IMMUNITY.name": "火系免疫",
-	"core.bonus.FIRE_IMMUNITY.description": "免疫所有火系魔法",
+	"core.bonus.FIRE_IMMUNITY.description": "免疫所有火系魔法",
 	"core.bonus.FIRE_SHIELD.name": "烈火神盾 (${val}%)",
 	"core.bonus.FIRE_SHIELD.description": "反弹部分受到的近战伤害",
 	"core.bonus.FIRST_STRIKE.name": "抢先反击",

+ 267 - 160
Mods/vcmi/config/vcmi/czech.json

@@ -29,16 +29,36 @@
 	"vcmi.capitalColors.5" : "Fialový",
 	"vcmi.capitalColors.6" : "Tyrkysový",
 	"vcmi.capitalColors.7" : "Růžový",
+
+	"vcmi.heroOverview.startingArmy" : "Počáteční jednotky",
+	"vcmi.heroOverview.warMachine" : "Bojové stroje",
+	"vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti",
+	"vcmi.heroOverview.spells" : "Kouzla",
 	
 	"vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky",
 	"vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou",
 	"vcmi.radialWheel.splitSingleUnit" : "Rozdělit jedinou jednotku",
-	"vcmi.radialWheel.splitUnitEqually" : "Rovnoměrně rozdělit jednotky",
+	"vcmi.radialWheel.splitUnitEqually" : "Rozdělit jednotky rovnoměrně",
 	"vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu",
 	"vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice",
 
+	"vcmi.radialWheel.heroGetArmy" : "Získat armádu jiného hrdiny",
+	"vcmi.radialWheel.heroSwapArmy" : "Vyměnit armádu s jiným hrdinou",
+	"vcmi.radialWheel.heroExchange" : "Otevřít výměnu hrdinů",
+	"vcmi.radialWheel.heroGetArtifacts" : "Získat artefakty od jiního hrdiny",
+	"vcmi.radialWheel.heroSwapArtifacts" : "Vyměnit artefakty s jiným hrdinou",
+	"vcmi.radialWheel.heroDismiss" : "Propustit hrdinu",
+
+	"vcmi.radialWheel.moveTop" : "Move to top",
+	"vcmi.radialWheel.moveUp" : "Move up",
+	"vcmi.radialWheel.moveDown" : "Move down",
+	"vcmi.radialWheel.moveBottom" : "Move to bottom",
+
+	"vcmi.spellBook.search" : "hledat...",
+
 	"vcmi.mainMenu.serverConnecting" : "Připojování...",
 	"vcmi.mainMenu.serverAddressEnter" : "Zadejte adresu:",
+	"vcmi.mainMenu.serverConnectionFailed" : "Připojování selhalo",
 	"vcmi.mainMenu.serverClosing" : "Zavírání...",
 	"vcmi.mainMenu.hostTCP" : "Pořádat hru TCP/IP",
 	"vcmi.mainMenu.joinTCP" : "Připojit se do hry TCP/IP",
@@ -46,17 +66,26 @@
 	
 	"vcmi.lobby.filepath" : "Název souboru",
 	"vcmi.lobby.creationDate" : "Datum vytvoření",
+	"vcmi.lobby.scenarioName" : "Název scénáře",
+	"vcmi.lobby.mapPreview" : "Náhled mapy",
+	"vcmi.lobby.noPreview" : "bez náhledu",
+	"vcmi.lobby.noUnderground" : "bez podzemí",
 
+	"vcmi.client.errors.missingCampaigns" : "{Chybějící datové soubory}\n\nDatové soubory kampaně nebyly nalezeny! Možná máte nekompletní nebo poškozené datové soubory Heroes 3. Prosíme, přeinstalujte hru.",
 	"vcmi.server.errors.existingProcess" : "Již běží jiný server VCMI. Prosím, ukončete ho před startem nové hry.",
 	"vcmi.server.errors.modsToEnable"    : "{Následující modifikace jsou nutné pro načtení hry}",
+	"vcmi.server.errors.modsToDisable"   : "{Následující modifikace musí být zakázány}",
 	"vcmi.server.confirmReconnect"       : "Chcete se připojit k poslední relaci?",
+	"vcmi.server.errors.modNoDependency" : "Nelze načíst modifikaci {'%s'}!\n Závisí na modifikaci {'%s'}, která není aktivní!\n",
+	"vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n",
+	"vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!",
 
 	"vcmi.settingsMainWindow.generalTab.hover" : "Obecné",
-	"vcmi.settingsMainWindow.generalTab.help"     : "Přepne na kartu obecných nastavení, která obsahuje nastavení související s obecným chováním klienta hry",
+	"vcmi.settingsMainWindow.generalTab.help"     : "Přepne na kartu obecných nastavení, která obsahuje nastavení související s obecným chováním klienta hry.",
 	"vcmi.settingsMainWindow.battleTab.hover" : "Bitva",
-	"vcmi.settingsMainWindow.battleTab.help"     : "Přepne na kartu nastavení bitvy, která umožňuje konfiguraci chování hry v bitvách",
+	"vcmi.settingsMainWindow.battleTab.help"     : "Přepne na kartu nastavení bitvy, která umožňuje konfiguraci chování hry v bitvách.",
 	"vcmi.settingsMainWindow.adventureTab.hover" : "Mapa světa",
-	"vcmi.settingsMainWindow.adventureTab.help"  : "Přepne na kartu nastavení mapy světa (mapa světa je sekce hry, ve které hráči mohou ovládat pohyb hrdinů)",
+	"vcmi.settingsMainWindow.adventureTab.help"  : "Přepne na kartu nastavení mapy světa (mapa světa je sekce hry, ve které hráči mohou ovládat pohyb hrdinů).",
 
 	"vcmi.systemOptions.videoGroup" : "Nastavení obrazu",
 	"vcmi.systemOptions.audioGroup" : "Nastavení zvuku",
@@ -81,24 +110,32 @@
 	"vcmi.systemOptions.longTouchMenu.help"      : "Změnit dobu dlouhého podržení.",
 	"vcmi.systemOptions.longTouchMenu.entry"     : "%d milisekund",
 	"vcmi.systemOptions.framerateButton.hover"  : "Zobrazit FPS",
-	"vcmi.systemOptions.framerateButton.help"   : "{Zobrazit FPS}\n\nPřepne viditelnost počitadla snímků za sekundu v rohu obrazovky hry",
+	"vcmi.systemOptions.framerateButton.help"   : "{Zobrazit FPS}\n\nPřepne viditelnost počitadla snímků za sekundu v rohu obrazovky hry.",
 	"vcmi.systemOptions.hapticFeedbackButton.hover"  : "Vibrace",
-	"vcmi.systemOptions.hapticFeedbackButton.help"   : "{Vibrace}\n\nPřepnout stav vibrací při dotykovém ovládání",
+	"vcmi.systemOptions.hapticFeedbackButton.help"   : "{Vibrace}\n\nPřepnout stav vibrací při dotykovém ovládání.",
+	"vcmi.systemOptions.enableUiEnhancementsButton.hover"  : "Vylepšení rozhraní",
+	"vcmi.systemOptions.enableUiEnhancementsButton.help"   : "{Vylepšení rozhraní}\n\nZapne různá vylepšení rozhraní, jako je tlačítko batohu atd. Zakažte pro zážitek klasické hry.",
+	"vcmi.systemOptions.enableLargeSpellbookButton.hover"  : "Velká kniha kouzel",
+	"vcmi.systemOptions.enableLargeSpellbookButton.help"   : "{Velká kniha kouzel}\n\nPovolí větší knihu kouzel, do které se jich více vleze na jednu stranu. Animace změny stránek s tímto nastavením nefunguje.",
+	"vcmi.systemOptions.audioMuteFocus.hover"  : "Ztlumit při neaktivitě",
+	"vcmi.systemOptions.audioMuteFocus.help"   : "{Ztlumit při neaktivitě}\n\nZtlumit zvuk, pokud je okno hry v pozadí. Výjimkou jsou zprávy ve hře a zvuk nového tahu.",
 
 	"vcmi.adventureOptions.infoBarPick.hover" : "Zobrazit zprávy v panelu informací",
 	"vcmi.adventureOptions.infoBarPick.help" : "{Zobrazit zprávy v panelu informací}\n\nKdyž bude možné, herní zprávy z návštěv míst na mapě budou zobrazeny v panelu informací místo ve zvláštním okně.",
 	"vcmi.adventureOptions.numericQuantities.hover" : "Číselné množství jednotek",
 	"vcmi.adventureOptions.numericQuantities.help" : "{Číselné množství jednotek}\n\nZobrazit přibližné množství nepřátelských jednotek ve formátu A-B.",
 	"vcmi.adventureOptions.forceMovementInfo.hover" : "Vždy zobrazit cenu pohybu",
-	"vcmi.adventureOptions.forceMovementInfo.help" : "{Vždy zobrazit cenu pohybu}\n\nVždy zobrazit informace o bodech pohybu v panelu informací. (Místo zobrazení pouze při stisknuté klávese ALT)",
+	"vcmi.adventureOptions.forceMovementInfo.help" : "{Vždy zobrazit cenu pohybu}\n\nVždy zobrazit informace o bodech pohybu v panelu informací. (Místo zobrazení pouze při stisknuté klávese ALT).",
 	"vcmi.adventureOptions.showGrid.hover" : "Zobrazit mřížku",
 	"vcmi.adventureOptions.showGrid.help" : "{Zobrazit mřížku}\n\nZobrazit překrytí mřížkou, zvýrazňuje hranice mezi dlaždicemi mapy světa.",
 	"vcmi.adventureOptions.borderScroll.hover" : "Posouvání okraji",
 	"vcmi.adventureOptions.borderScroll.help" : "{Posouvání okraji}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.",
 	"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Info Panel Creature Management", //TODO
-	"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Info Panel Creature Management}\n\nAllows rearranging creatures in info panel instead of cycling between default components",
-	"vcmi.adventureOptions.leftButtonDrag.hover" : "Left Click Drag Map",
-	"vcmi.adventureOptions.leftButtonDrag.help" : "{Left Click Drag Map}\n\nWhen enabled, moving mouse with left button pressed will drag adventure map view",
+	"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Info Panel Creature Management}\n\nAllows rearranging creatures in info panel instead of cycling between default components.",
+	"vcmi.adventureOptions.leftButtonDrag.hover" : "Posouvání mapy levým kliknutím",
+	"vcmi.adventureOptions.leftButtonDrag.help" : "{Posouvání mapy levým kliknutím}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.",
+	"vcmi.adventureOptions.smoothDragging.hover" : "Plynulé posouvání mapy",
+	"vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nWhen enabled, map dragging has a modern run out effect.", // TODO
 	"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
@@ -111,16 +148,16 @@
 	"vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO",
 	"vcmi.battleOptions.queueSizeSmallButton.hover": "MALÁ",
 	"vcmi.battleOptions.queueSizeBigButton.hover": "VELKÁ",
-	"vcmi.battleOptions.queueSizeNoneButton.help": "Nezobrazovat frontu pořadí tahů",
+	"vcmi.battleOptions.queueSizeNoneButton.help": "Nezobrazovat frontu pořadí tahů.",
 	"vcmi.battleOptions.queueSizeAutoButton.help": "Nastavit automaticky velikost fronty pořadí tahů podle rozlišení obrazovky hry (Při výšce herního rozlišení menší než 700 pixelů je použita velikost MALÁ, jinak velikost VELKÁ)",
-	"vcmi.battleOptions.queueSizeSmallButton.help": "Zobrazit malou frontu pořadí tahů",
-	"vcmi.battleOptions.queueSizeBigButton.help": "Zobrazit velkou frontu pořadí tahů (není podporováno, pokud výška rozlišení hry není alespoň 700 pixelů)",
+	"vcmi.battleOptions.queueSizeSmallButton.help": "Zobrazit MALOU frontu pořadí tahů.",
+	"vcmi.battleOptions.queueSizeBigButton.help": "Zobrazit VELKOU frontu pořadí tahů (není podporováno, pokud výška rozlišení hry není alespoň 700 pixelů).",
 	"vcmi.battleOptions.animationsSpeed1.hover": "",
 	"vcmi.battleOptions.animationsSpeed5.hover": "",
 	"vcmi.battleOptions.animationsSpeed6.hover": "",
-	"vcmi.battleOptions.animationsSpeed1.help": "Nastavit rychlost animací na velmi pomalé",
-	"vcmi.battleOptions.animationsSpeed5.help": "Nastavit rychlost animací na velmi rychlé",
-	"vcmi.battleOptions.animationsSpeed6.help": "Nastavit rychlost animací na okamžité",
+	"vcmi.battleOptions.animationsSpeed1.help": "Nastavit rychlost animací na velmi pomalé.",
+	"vcmi.battleOptions.animationsSpeed5.help": "Nastavit rychlost animací na velmi rychlé.",
+	"vcmi.battleOptions.animationsSpeed6.help": "Nastavit rychlost animací na okamžité.",
 	"vcmi.battleOptions.movementHighlightOnHover.hover": "Zvýraznění pohybu při najetí",
 	"vcmi.battleOptions.movementHighlightOnHover.help": "{Zvýraznění pohybu při najetí}\n\nZvýraznit rozsah pohybu jednotky při najetí na něj.",
 	"vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Zobrazit omezení dostřelu střelců",
@@ -130,6 +167,9 @@
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Přeskočit úvodní hudbu",
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Přeskočit úvodní hudbu}\n\nPovolí akce při úvodní hudbě přehrávané při začátku každé bitvy.",
 
+	"vcmi.adventureMap.revisitObject.hover" : "Znovu navštívit místo",
+	"vcmi.adventureMap.revisitObject.help" : "{Znovu navštívit místo}\n\nPokud se hrdina nachází na nějakém místě mapy, může jej znovu navštívit.",
+
 	"vcmi.battleWindow.pressKeyToSkipIntro" : "Stiskněte jakoukoliv klávesu pro okamžité zahájení bitvy",
 	"vcmi.battleWindow.damageEstimation.melee" : "Zaútočit na %CREATURE (%DAMAGE).",
 	"vcmi.battleWindow.damageEstimation.meleeKills" : "Zaútočit na %CREATURE (%DAMAGE, %KILLS).",
@@ -144,6 +184,15 @@
 
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy",
 
+	"vcmi.tutorialWindow.title" : "Úvod ovládání dotykem",
+	"vcmi.tutorialWindow.decription.RightClick" : "Klepněte a držte prvek, na který byste chtěli použít pravé tlačítko myši. Klepněte na volnou oblast pro zavření.",
+	"vcmi.tutorialWindow.decription.MapPanning" : "Klepněte a držte jedním prstem pro posouvání mapy.",
+	"vcmi.tutorialWindow.decription.MapZooming" : "Přibližte dva prsty k sobě pro přiblížení mapy.",
+	"vcmi.tutorialWindow.decription.RadialWheel" : "Přejetí otevře kruhovou nabídku pro různé akce, třeba správa hrdiny/bojovnínků a příkazy měst.",
+	"vcmi.tutorialWindow.decription.BattleDirection" : "Pro útok ze speifického úhlu, přejeďte směrem, ze kterého má být útok vykonán.",
+	"vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Gesto útoku pod úhlem může být zrušeno, pokud, pokud je prst dostatečně daleko.",
+	"vcmi.tutorialWindow.decription.AbortSpell" : "Klepněte a držte pro zrušení kouzla.",
+
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Zobrazit dostupné jednotky",
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Zobrazit dostupné jednotky}\n\nZobrazit počet jednotek dostupných ke koupení místo jejich týdenního přírůstku v přehledu města. (levý spodní okraj obrazovky města).",
 	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Zobrazit týdenní přírůstek jednotek",
@@ -152,49 +201,107 @@
 	"vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompaktní informace o jednotkách}\n\nZobrazit menší informace o jednotkách města v jeho přehledu (levý spodní okraj obrazovky města).",
 
 	"vcmi.townHall.missingBase"             : "Základní budova %s musí být postavena jako první",
-	"vcmi.townHall.noCreaturesToRecruit"    : "Žádné jednotky k vycvičení!", //TODO
-	"vcmi.townHall.greetingManaVortex"      : "As you near the %s your body is filled with new energy. You have doubled your normal spell points.",
-	"vcmi.townHall.greetingKnowledge"       : "You study the glyphs on the %s and gain insight into the workings of various magics (+1 Knowledge).",
+	"vcmi.townHall.noCreaturesToRecruit"    : "Žádné jednotky k vycvičení!",
+	"vcmi.townHall.greetingManaVortex"      : "Při pobytu u místa %s se vaše tělo naplnilo novou energií. Máte dvojnásobné množství maximální magické energie.",
+	"vcmi.townHall.greetingKnowledge"       : "Studujete glyfy na the %s a porozumíte fungování různých magií (+1 Znalosti).",
 	"vcmi.townHall.greetingSpellPower"      : "%s vás učí nové cesty zaměření vaší magické síly (+1 Síla kouzel).",
 	"vcmi.townHall.greetingExperience"      : "Návštěva %s vás naučila spoustu nových dovedností (+1000 zkušeností).",
-	"vcmi.townHall.greetingAttack"          : "Čas strávený poblíž místa zvaného %s allows you to learn more effective combat skills (+1 Attack Skill).",
-	"vcmi.townHall.greetingDefence"         : "Spending time in the %s, the experienced warriors therein teach you additional defensive skills (+1 Defense).",
-	"vcmi.townHall.hasNotProduced"          : "%s zatím nic nevyrobil.",
-	"vcmi.townHall.hasProduced"             : "%s vyrobil %d %s tento týden.",
+	"vcmi.townHall.greetingAttack"          : "Čas strávený poblíž místa zvaného %s vám dovolil se naučit efektivnější bojové dovednosti (+1 Útočná síla).",
+	"vcmi.townHall.greetingDefence"         : "Trávíte čas na místě zvaném %s, zkušení bojovníci vás u toho naučili nové metody obrany (+1 Obranná síla).",
+	"vcmi.townHall.hasNotProduced"          : "%s - zatím nic nevyrobeno.",
+	"vcmi.townHall.hasProduced"             : "%s - vyrobeno %d %s tento týden.",
 	"vcmi.townHall.greetingCustomBonus"     : "%s vám dává +%d %s%s",
 	"vcmi.townHall.greetingCustomUntil"     : " do další bitvy.",
-	"vcmi.townHall.greetingInTownMagicWell" : "%s obnovil na maximum vaši magickou energii.",
+	"vcmi.townHall.greetingInTownMagicWell" : "%s - obnoveno na maximum vaši magickou energii.",
 
 	"vcmi.logicalExpressions.anyOf"  : "Něco z následujících:",
 	"vcmi.logicalExpressions.allOf"  : "Všechny následující:",
 	"vcmi.logicalExpressions.noneOf" : "Žádné z následujících:",
 
 	"vcmi.heroWindow.openCommander.hover" : "Open commander info window",
-	"vcmi.heroWindow.openCommander.help"  : "Shows details about the commander of this hero",
+	"vcmi.heroWindow.openCommander.help"  : "Shows details about the commander of this hero.",
 	"vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window",
-	"vcmi.heroWindow.openBackpack.help"  : "Opens window that allows easier artifact backpack management",
+	"vcmi.heroWindow.openBackpack.help"  : "Opens window that allows easier artifact backpack management.",
 
-	"vcmi.commanderWindow.artifactMessage" : "Do you want to return this artifact to the hero?",
+	"vcmi.commanderWindow.artifactMessage" : "Chcete navrátit tento artefakt hrdinovi?",
 
-	"vcmi.creatureWindow.showBonuses.hover"    : "Switch to bonuses view",
-	"vcmi.creatureWindow.showBonuses.help"     : "Display all active bonuses of the commander",
-	"vcmi.creatureWindow.showSkills.hover"     : "Switch to skills view",
-	"vcmi.creatureWindow.showSkills.help"      : "Display all learned skills of the commander",
+	"vcmi.creatureWindow.showBonuses.hover"    : "Přepnout na zobrazení bonusů",
+	"vcmi.creatureWindow.showBonuses.help"     : "Display all active bonuses of the commander.",
+	"vcmi.creatureWindow.showSkills.hover"     : "Přepnout na zobrazení schoostí",
+	"vcmi.creatureWindow.showSkills.help"      : "Display all learned skills of the commander.",
 	"vcmi.creatureWindow.returnArtifact.hover" : "Vrátit artefakt",
-	"vcmi.creatureWindow.returnArtifact.help"  : "Klikněte na toto tlačítko pro navrácení artefaktů do hrdinova batohu",
+	"vcmi.creatureWindow.returnArtifact.help"  : "Klikněte na toto tlačítko pro navrácení artefaktů do hrdinova batohu.",
 
-	"vcmi.questLog.hideComplete.hover" : "Hide complete quests",
-	"vcmi.questLog.hideComplete.help"  : "Hide all completed quests",
+	"vcmi.questLog.hideComplete.hover" : "Skrýt dokončené úkoly",
+	"vcmi.questLog.hideComplete.help"  : "Skrýt všechny dokončené úkoly.",
 
-	"vcmi.randomMapTab.widgets.randomTemplate"      : "(Random)",
+	"vcmi.randomMapTab.widgets.randomTemplate"      : "(Náhodná)",
 	"vcmi.randomMapTab.widgets.templateLabel"        : "Šablona",
-	"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Setup...",
-	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Team Alignments",
+	"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Nastavit...",
+	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Přiřazení týmů",
 	"vcmi.randomMapTab.widgets.roadTypesLabel"       : "Druhy cest",
 
-	"vcmi.optionsTab.widgets.labelTimer" : "Časovač",
-	"vcmi.optionsTab.widgets.timerModeSwitch.classic" : "Klasický časovač",
-	"vcmi.optionsTab.widgets.timerModeSwitch.chess" : "Šachový časovač",
+	"vcmi.optionsTab.turnOptions.hover" : "Možnosti tahu",
+	"vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo tahů a nastavení souběžných tahů",
+	"vcmi.optionsTab.selectPreset" : "Preset",
+
+	"vcmi.optionsTab.chessFieldBase.hover" : "Base timer",
+	"vcmi.optionsTab.chessFieldTurn.hover" : "Turn timer",
+	"vcmi.optionsTab.chessFieldBattle.hover" : "Battle 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.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.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.accumulate" : "Accumulate",
+
+	"vcmi.optionsTab.simturnsTitle" : "Souběžné tahy",
+	"vcmi.optionsTab.simturnsMin.hover" : "Alespoň po",
+	"vcmi.optionsTab.simturnsMax.hover" : "Nejvíce po",
+	"vcmi.optionsTab.simturnsAI.hover" : "(Experimentální) Souběžné tahy AI",
+	"vcmi.optionsTab.simturnsMin.help" : "Hrát souběžně po určený počet dní. Setkání mezi hráči je v této době zablokováno",
+	"vcmi.optionsTab.simturnsMax.help" : "Hrát souběžně po určený počet dní nebo do setkání s jiným hráčem",
+	"vcmi.optionsTab.simturnsAI.help" : "{Souběžné tahy AI}\nExperimentální volba. Dovoluje AI hráčům hrát souběžně s lidskými hráči, když jsou souběžné tahy povoleny.",
+
+	"vcmi.optionsTab.turnTime.select"     : "Vyberte šablonu nastavení časovače",
+	"vcmi.optionsTab.turnTime.unlimited"  : "Neomezený čas tahu",
+	"vcmi.optionsTab.turnTime.classic.1"  : "Klasický časovač: 1 minuta",
+	"vcmi.optionsTab.turnTime.classic.2"  : "Klasický časovač: 2 minuty",
+	"vcmi.optionsTab.turnTime.classic.5"  : "Klasický časovač: 5 minut",
+	"vcmi.optionsTab.turnTime.classic.10" : "Klasický časovač: 10 minut",
+	"vcmi.optionsTab.turnTime.classic.20" : "Klasický časovač: 20 minut",
+	"vcmi.optionsTab.turnTime.classic.30" : "Klasický časovač: 30 minut",
+	"vcmi.optionsTab.turnTime.chess.20"   : "Šachová: 20:00 + 10:00 + 02:00 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.16"   : "Šachová: 16:00 + 08:00 + 01:30 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.8"    : "Šachová: 08:00 + 04:00 + 01:00 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.4"    : "Šachová: 04:00 + 02:00 + 00:30 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.2"    : "Šachová: 02:00 + 01:00 + 00:15 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.1"    : "Šachová: 01:00 + 01:00 + 00:00 + 00:00",
+
+	"vcmi.optionsTab.simturns.select"         : "Vyberte šablonu souběžných tahů",
+	"vcmi.optionsTab.simturns.none"           : "Bez souběžných tahů",
+	"vcmi.optionsTab.simturns.tillContactMax" : "Souběžně: Do setkání",
+	"vcmi.optionsTab.simturns.tillContact1"   : "Souběžně: 1 týden, přerušit při setkání",
+	"vcmi.optionsTab.simturns.tillContact2"   : "Souběžně: 2 týdny, přerušit při setkání",
+	"vcmi.optionsTab.simturns.tillContact4"   : "Souběžně: 1 mšsíc, přerušit při setkání",
+	"vcmi.optionsTab.simturns.blocked1"       : "Souběžně: 1 týden, setkání zablokována",
+	"vcmi.optionsTab.simturns.blocked2"       : "Souběžně: 2 týdny, setkání zablokována",
+	"vcmi.optionsTab.simturns.blocked4"       : "Souběžně: 1 měsíc, setkání zablokována",
+	
+	// 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 dní",
+	"vcmi.optionsTab.simturns.days.1" : " %d den",
+	"vcmi.optionsTab.simturns.days.2" : " %d dny",
+	"vcmi.optionsTab.simturns.weeks.0" : " %d týdnů",
+	"vcmi.optionsTab.simturns.weeks.1" : " %d týden",
+	"vcmi.optionsTab.simturns.weeks.2" : " %d týdny",
+	"vcmi.optionsTab.simturns.months.0" : " %d měsíců",
+	"vcmi.optionsTab.simturns.months.1" : " %d měsíc",
+	"vcmi.optionsTab.simturns.months.2" : " %d měsíce",
 
 	// Custom victory conditions for H3 campaigns and HotA maps
 	"vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!",
@@ -203,162 +310,162 @@
 	"vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Gratulace! Porazili jste všechny nepřátele zamořující tuto zemi a můžete si nárokovat vítězství!",
 	"vcmi.map.victoryCondition.collectArtifacts.message" : "Získejte tři artefakty",
 	"vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Gratulace! Všichni vaši nepřítelé byli poraženi a máte Andělskou alianci! Vítězství je vaše!",
-	"vcmi.map.victoryCondition.angelicAlliance.message" : "Porazte všechny nepřátele a vyrobte Andělskou alianci",
+	"vcmi.map.victoryCondition.angelicAlliance.message" : "Porazte všechny nepřátele a utužte Andělskou alianci",
 
 	// few strings from WoG used by vcmi
-	"vcmi.stackExperience.description" : "» S t a c k   E x p e r i e n c e   D e t a i l s «\n\nCreature Type ................... : %s\nExperience Rank ................. : %s (%i)\nExperience Points ............... : %i\nExperience Points to Next Rank .. : %i\nMaximum Experience per Battle ... : %i%% (%i)\nNumber of Creatures in stack .... : %i\nMaximum New Recruits\n without losing current Rank .... : %i\nExperience Multiplier ........... : %.2f\nUpgrade Multiplier .............. : %.2f\nExperience after Rank 10 ........ : %i\nMaximum New Recruits to remain at\n Rank 10 if at Maximum Experience : %i",
-	"vcmi.stackExperience.rank.0" : "Basic",
-	"vcmi.stackExperience.rank.1" : "Novice",
-	"vcmi.stackExperience.rank.2" : "Trained",
-	"vcmi.stackExperience.rank.3" : "Skilled",
-	"vcmi.stackExperience.rank.4" : "Proven",
-	"vcmi.stackExperience.rank.5" : "Veteran",
+	"vcmi.stackExperience.description" : "» P o d r o b n o s t i   z k u š e n o s t í   o d d í l u «\n\nDruh bojovníka ................... : %s\nÚroveň hodnosti ................. : %s (%i)\nBody zkušeností ............... : %i\nZkušenostních bodů do další úrovně hodnosti .. : %i\nMaximum zkušeností na bitvu ... : %i%% (%i)\nPočet bojovníků v oddílu .... : %i\nMaximum nových rekrutů\n bez ztráty současné hodnosti .... : %i\nNásobič zkušeností ........... : %.2f\nNásobič vylepšení .............. : %.2f\nZkušnosti po 10. úrovně hodnosti ........ : %i\nMaximální počet nových rekrutů pro zachování\n 10. úrovně hodnosti s maximálními zkušenostmi: %i",
+	"vcmi.stackExperience.rank.0" : "Začátečník",
+	"vcmi.stackExperience.rank.1" : "Učeň",
+	"vcmi.stackExperience.rank.2" : "Trénovaný",
+	"vcmi.stackExperience.rank.3" : "Zručný",
+	"vcmi.stackExperience.rank.4" : "Prověřený",
+	"vcmi.stackExperience.rank.5" : "Veterán",
 	"vcmi.stackExperience.rank.6" : "Adept",
 	"vcmi.stackExperience.rank.7" : "Expert",
-	"vcmi.stackExperience.rank.8" : "Elite",
-	"vcmi.stackExperience.rank.9" : "Master",
-	"vcmi.stackExperience.rank.10" : "Ace",
+	"vcmi.stackExperience.rank.8" : "Elit",
+	"vcmi.stackExperience.rank.9" : "Mistr",
+	"vcmi.stackExperience.rank.10" : "Eso",
 	
 	"core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý úder",
 	"core.bonus.ADDITIONAL_ATTACK.description": "Útočí dvakrát",
-	"core.bonus.ADDITIONAL_RETALIATION.name": "Additional retaliations",
-	"core.bonus.ADDITIONAL_RETALIATION.description": "May retaliate ${val} extra times",
+	"core.bonus.ADDITIONAL_RETALIATION.name": "Další odveta",
+	"core.bonus.ADDITIONAL_RETALIATION.description": "Může zaútočit zpět navíc ${val}x",
 	"core.bonus.AIR_IMMUNITY.name": "Vzdušná odolnost",
-	"core.bonus.AIR_IMMUNITY.description": "Immune to all spells from the school of Air magic",
+	"core.bonus.AIR_IMMUNITY.description": "Imunní všem kouzlům školy vzdušné magie",
 	"core.bonus.ATTACKS_ALL_ADJACENT.name": "Útok okolo",
 	"core.bonus.ATTACKS_ALL_ADJACENT.description": "Útočí na všechny sousední jednotky",
-	"core.bonus.BLOCKS_RETALIATION.name": "No retaliation",
-	"core.bonus.BLOCKS_RETALIATION.description": "Enemy cannot retaliate",
-	"core.bonus.BLOCKS_RANGED_RETALIATION.name": "No ranged retaliation",
-	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Enemy cannot retaliate by using a ranged attack",
+	"core.bonus.BLOCKS_RETALIATION.name": "Žádná odplata",
+	"core.bonus.BLOCKS_RETALIATION.description": "Nepřítel nemůže zaútočit zpět",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.name": "Žádná odplata na dálku",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Nepřítel nemůže zaútočit zpět útokem na dálku",
 	"core.bonus.CATAPULT.name": "Katapult",
-	"core.bonus.CATAPULT.description": "Attacks siege walls",
-	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reduce Casting Cost (${val})",
-	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Reduces the spellcasting cost for the hero by ${val}",
-	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Magic Damper (${val})",
-	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Increases spellcasting cost of enemy spells by ${val}",
-	"core.bonus.CHARGE_IMMUNITY.name": "Immune to Charge",
+	"core.bonus.CATAPULT.description": "Útočí na ochranné hradby",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Snížit cenu kouzel (${val})",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Snižuje cenu energie hrdiny o ${val}",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Tlumič magie (${val})",
+	"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zvyšuje cenu energie kouzlení nepřítele o ${val}",
+	"core.bonus.CHARGE_IMMUNITY.name": "Immune to Charge", // TODO
 	"core.bonus.CHARGE_IMMUNITY.description": "Immune to Cavalier's and Champion's Charge",
-	"core.bonus.DARKNESS.name": "Darkness cover",
-	"core.bonus.DARKNESS.description": "Creates a shroud of darkness with a ${val} radius",
-	"core.bonus.DEATH_STARE.name": "Death Stare (${val}%)",
-	"core.bonus.DEATH_STARE.description": "Has a ${val}% chance to kill a single creature",
-	"core.bonus.DEFENSIVE_STANCE.name": "Defense Bonus",
-	"core.bonus.DEFENSIVE_STANCE.description": "+${val} Defense when defending",
-	"core.bonus.DESTRUCTION.name": "Destruction",
-	"core.bonus.DESTRUCTION.description": "Has ${val}% chance to kill extra units after attack",
-	"core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Death Blow",
-	"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Has a ${val}% chance of dealing double base damage when attacking",
+	"core.bonus.DARKNESS.name": "Závoj temnoty",
+	"core.bonus.DARKNESS.description": "Vytvoří clonu temnoty v oblasti ${val} polí",
+	"core.bonus.DEATH_STARE.name": "Smrtící pohled (${val}%)",
+	"core.bonus.DEATH_STARE.description": "Má ${val}% šanci zabít jednu creature",
+	"core.bonus.DEFENSIVE_STANCE.name": "Obranný bonus",
+	"core.bonus.DEFENSIVE_STANCE.description": "+${val} obranné síly při obraně",
+	"core.bonus.DESTRUCTION.name": "Zničení",
+	"core.bonus.DESTRUCTION.description": "Má ${val}% šanci zabít další jednotky po útoku",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Smrtící rána",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Má ${val}% šanci na udělení dvojnásobného základního poškození při útoku",
 	"core.bonus.DRAGON_NATURE.name": "Drak",
-	"core.bonus.DRAGON_NATURE.description": "Creature has a Dragon Nature",
+	"core.bonus.DRAGON_NATURE.description": "Jednotka má povahu draka",
 	"core.bonus.EARTH_IMMUNITY.name": "Zemní odolnost",
-	"core.bonus.EARTH_IMMUNITY.description": "Immune to all spells from the school of Earth magic",
-	"core.bonus.ENCHANTER.name": "Enchanter",
-	"core.bonus.ENCHANTER.description": "Can cast mass ${subtype.spell} every turn",
-	"core.bonus.ENCHANTED.name": "Enchanted",
-	"core.bonus.ENCHANTED.description": "Affected by permanent ${subtype.spell}",
-	"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignore Defense (${val}%)",
-	"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "When attacking, ${val}% of the defender's defense is ignored",
+	"core.bonus.EARTH_IMMUNITY.description": "Imunní všem kouzlům školy zemské magie",
+	"core.bonus.ENCHANTER.name": "Zaklínač",
+	"core.bonus.ENCHANTER.description": "Může masově seslat ${subtype.spell} každý tah",
+	"core.bonus.ENCHANTED.name": "Očarovaný",
+	"core.bonus.ENCHANTED.description": "Trvale ovlivněm kouzlem ${subtype.spell}",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Nevšímá si ${val} % bodů obrany",
+	"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Pří útoku nebude brát v potaz ${val}% bodů obrany obránce",
 	"core.bonus.FIRE_IMMUNITY.name": "Ohnivá odolnost",
-	"core.bonus.FIRE_IMMUNITY.description": "Immune to all spells from the school of Fire magic",
+	"core.bonus.FIRE_IMMUNITY.description": "Imunní všem kouzlům školy ohnivé magie",
 	"core.bonus.FIRE_SHIELD.name": "Ohnivý štít (${val}%)",
-	"core.bonus.FIRE_SHIELD.description": "Reflects part of melee damage",
-	"core.bonus.FIRST_STRIKE.name": "First Strike",
-	"core.bonus.FIRST_STRIKE.description": "This creature retaliates before being attacked",
-	"core.bonus.FEAR.name": "Fear",
-	"core.bonus.FEAR.description": "Causes Fear on an enemy stack",
+	"core.bonus.FIRE_SHIELD.description": "Odrazí část zranení útoku zblízka",
+	"core.bonus.FIRST_STRIKE.name": "První úder",
+	"core.bonus.FIRST_STRIKE.description": "Tato jednotka útočí zpět ještě než je na ni zaútočeno",
+	"core.bonus.FEAR.name": "Strach",
+	"core.bonus.FEAR.description": "Způsobí strach nepřátelskému oddílu",
 	"core.bonus.FEARLESS.name": "Nebojácnost",
 	"core.bonus.FEARLESS.description": "Odolnost proti strachu",
 	"core.bonus.FLYING.name": "Letec",
 	"core.bonus.FLYING.description": "Při pohybu létá (přes překážky)",
 	"core.bonus.FREE_SHOOTING.name": "Blízké výstřely",
 	"core.bonus.FREE_SHOOTING.description": "Může použít výstřely při útoku zblízka",
-	"core.bonus.GARGOYLE.name": "Gargoyle",
-	"core.bonus.GARGOYLE.description": "Cannot be raised or healed",
-	"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Reduce Damage (${val}%)",
-	"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Reduces physical damage from ranged or melee attacks",
+	"core.bonus.GARGOYLE.name": "Chrlič",
+	"core.bonus.GARGOYLE.description": "Cannot be raised or healed", // TODO
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Snižuje poškození (${val}%)",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Snižuje poškození od útoků z dálky a blízka",
 	"core.bonus.HATE.name": "Nesnáší ${subtype.creature}",
-	"core.bonus.HATE.description": "Does ${val}% more damage to ${subtype.creature}",
+	"core.bonus.HATE.description": "Dává o ${val} % větší zranění jednotce ${subtype.creature}",
 	"core.bonus.HEALER.name": "Léčitel",
 	"core.bonus.HEALER.description": "Léčí spojenecké jednotky",
 	"core.bonus.HP_REGENERATION.name": "Regenerace",
 	"core.bonus.HP_REGENERATION.description": "Každé kolo léčí ${val} životů",
-	"core.bonus.JOUSTING.name": "Champion charge",
-	"core.bonus.JOUSTING.description": "+${val}% damage for each hex travelled",
+	"core.bonus.JOUSTING.name": "Nabití šampiona",
+	"core.bonus.JOUSTING.description": "+${val}% poškození za každé projité pole",
 	"core.bonus.KING.name": "Král",
-	"core.bonus.KING.description": "Vulnerable to SLAYER level ${val} or higher",
-	"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Spell immunity 1-${val}",
-	"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Immune to spells of levels 1-${val}",
-	"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Limited shooting range",
-	"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Unable to target units farther than ${val} hexes",
+	"core.bonus.KING.description": "Zranitelný zabijákovi úrovně ${val} a vyšší",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odolnost kouzel 1-${val}",
+	"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odolnost vůči kouzlům úrovní 1-${val}",
+	"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Omezený dosah střelby",
+	"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Nevystřelí na jednotky dále než ${val} polí",
 	"core.bonus.LIFE_DRAIN.name": "Vysátí životů (${val}%)",
 	"core.bonus.LIFE_DRAIN.description": "Vysaje ${val}% uděleného poškození",
-	"core.bonus.MANA_CHANNELING.name": "Magic Channel ${val}%",
-	"core.bonus.MANA_CHANNELING.description": "Gives your hero ${val}% of the mana spent by the enemy",
+	"core.bonus.MANA_CHANNELING.name": "${val}% kouzelný kanál",
+	"core.bonus.MANA_CHANNELING.description": "Dá vašemu hrdinovi ${val} % many využité nepřítelem",
 	"core.bonus.MANA_DRAIN.name": "Vysátí many",
 	"core.bonus.MANA_DRAIN.description": "Každé kolo vysaje ${val} many",
 	"core.bonus.MAGIC_MIRROR.name": "Kouzelné zrcadlo (${val}%)",
-	"core.bonus.MAGIC_MIRROR.description": "Has a ${val}% chance to redirect an offensive spell to an enemy unit",
-	"core.bonus.MAGIC_RESISTANCE.name": "Magic Resistance (${val}%)",
-	"core.bonus.MAGIC_RESISTANCE.description": "Has a ${val}% chance to resist an enemy spell",
-	"core.bonus.MIND_IMMUNITY.name": "Mind Spell Immunity",
-	"core.bonus.MIND_IMMUNITY.description": "Immune to Mind-type spells",
-	"core.bonus.NO_DISTANCE_PENALTY.name": "No distance penalty",
-	"core.bonus.NO_DISTANCE_PENALTY.description": "Does full damage at any distance",
-	"core.bonus.NO_MELEE_PENALTY.name": "No melee penalty",
-	"core.bonus.NO_MELEE_PENALTY.description": "Creature has no Melee Penalty",
-	"core.bonus.NO_MORALE.name": "Neutral Morale",
-	"core.bonus.NO_MORALE.description": "Creature is immune to morale effects",
-	"core.bonus.NO_WALL_PENALTY.name": "No wall penalty",
-	"core.bonus.NO_WALL_PENALTY.description": "Full damage during siege",
-	"core.bonus.NON_LIVING.name": "Non living",
-	"core.bonus.NON_LIVING.description": "Immunity to many effects",
-	"core.bonus.RANDOM_SPELLCASTER.name": "Random spellcaster",
-	"core.bonus.RANDOM_SPELLCASTER.description": "Can cast random spell",
-	"core.bonus.RANGED_RETALIATION.name": "Ranged retaliation",
-	"core.bonus.RANGED_RETALIATION.description": "Can perform ranged counterattack",
-	"core.bonus.RECEPTIVE.name": "Receptive",
-	"core.bonus.RECEPTIVE.description": "No Immunity to Friendly Spells",
-	"core.bonus.REBIRTH.name": "Rebirth (${val}%)",
-	"core.bonus.REBIRTH.description": "${val}% of stack will rise after death",
-	"core.bonus.RETURN_AFTER_STRIKE.name": "Attack and Return",
-	"core.bonus.RETURN_AFTER_STRIKE.description": "Returns after melee attack",
-	"core.bonus.SHOOTER.name": "Ranged",
-	"core.bonus.SHOOTER.description": "Creature can shoot",
-	"core.bonus.SHOOTS_ALL_ADJACENT.name": "Shoot all around",
-	"core.bonus.SHOOTS_ALL_ADJACENT.description": "This creature's ranged attacks strike all targets in a small area",
-	"core.bonus.SOUL_STEAL.name": "Soul Steal",
-	"core.bonus.SOUL_STEAL.description": "Gains ${val} new creatures for each enemy killed",
-	"core.bonus.SPELLCASTER.name": "Spellcaster",
-	"core.bonus.SPELLCASTER.description": "Can cast ${subtype.spell}",
-	"core.bonus.SPELL_AFTER_ATTACK.name": "Cast After Attack",
-	"core.bonus.SPELL_AFTER_ATTACK.description": "Has a ${val}% chance to cast ${subtype.spell} after it attacks",
-	"core.bonus.SPELL_BEFORE_ATTACK.name": "Cast Before Attack",
-	"core.bonus.SPELL_BEFORE_ATTACK.description": "Has a ${val}% chance to cast ${subtype.spell} before it attacks",
-	"core.bonus.SPELL_DAMAGE_REDUCTION.name": "Spell Resistance",
-	"core.bonus.SPELL_DAMAGE_REDUCTION.description": "Damage from spells reduced by ${val}%.",
+	"core.bonus.MAGIC_MIRROR.description": "Má ${val}% šanci odrazit útočné kouzlo na nepřátelskou jednotku",
+	"core.bonus.MAGIC_RESISTANCE.name": "Magická odolnost (${val}%)",
+	"core.bonus.MAGIC_RESISTANCE.description": "Má ${val}% šanci ustát nepřátelské kouzlo",
+	"core.bonus.MIND_IMMUNITY.name": "Imunita kouzel mysli",
+	"core.bonus.MIND_IMMUNITY.description": "Imunní vůči kouzlům cílícím na mysl",
+	"core.bonus.NO_DISTANCE_PENALTY.name": "Bez penalizace vzdálenosti",
+	"core.bonus.NO_DISTANCE_PENALTY.description": "Plné poškození na jakoukoliv vzdálenost",
+	"core.bonus.NO_MELEE_PENALTY.name": "Bez penalizace útoku zblízka",
+	"core.bonus.NO_MELEE_PENALTY.description": "Jednotka není penalizována za útok zblízka",
+	"core.bonus.NO_MORALE.name": "Neutrální morálka",
+	"core.bonus.NO_MORALE.description": "Jednotka je imunní vůči efektu morálky",
+	"core.bonus.NO_WALL_PENALTY.name": "Bez penalizace hradbami",
+	"core.bonus.NO_WALL_PENALTY.description": "Plné poškození při obléhání",
+	"core.bonus.NON_LIVING.name": "Neživoucí",
+	"core.bonus.NON_LIVING.description": "Imunní vůči mnohým efektům",
+	"core.bonus.RANDOM_SPELLCASTER.name": "Náhodný kouzelník",
+	"core.bonus.RANDOM_SPELLCASTER.description": "Může seslat náhodné kouzlo",
+	"core.bonus.RANGED_RETALIATION.name": "Vzdálená msta",
+	"core.bonus.RANGED_RETALIATION.description": "Může provést protiútok na dálku",
+	"core.bonus.RECEPTIVE.name": "Přijímavý",
+	"core.bonus.RECEPTIVE.description": "Není imunní vůči přátelským kouzlům",
+	"core.bonus.REBIRTH.name": "Znovuzrození (${val}%)",
+	"core.bonus.REBIRTH.description": "${val}% oddílu se po smrti znovu narodí",
+	"core.bonus.RETURN_AFTER_STRIKE.name": "Útok a návrat",
+	"core.bonus.RETURN_AFTER_STRIKE.description": "Navrátí se po útoku na blízko",
+	"core.bonus.SHOOTER.name": "Střelec",
+	"core.bonus.SHOOTER.description": "Jednotka může střílet",
+	"core.bonus.SHOOTS_ALL_ADJACENT.name": "Střílí okolo",
+	"core.bonus.SHOOTS_ALL_ADJACENT.description": "Vzdálené útoky této jednotky zasáhnou všechny cíle v malé oblasti",
+	"core.bonus.SOUL_STEAL.name": "Zloděj duší",
+	"core.bonus.SOUL_STEAL.description": "Získá ${val} nových jednotek za každého zabitého nepřítele",
+	"core.bonus.SPELLCASTER.name": "Kouzelník",
+	"core.bonus.SPELLCASTER.description": "Může seslat ${subtype.spell}",
+	"core.bonus.SPELL_AFTER_ATTACK.name": "Kouzlení po útoku",
+	"core.bonus.SPELL_AFTER_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} po zaútočení",
+	"core.bonus.SPELL_BEFORE_ATTACK.name": "Kouzlení před útokem",
+	"core.bonus.SPELL_BEFORE_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} před zaútočením",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.name": "Magická odolnost",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.description": "Zranění od kouzel sníženo o ${val}%.",
 	"core.bonus.SPELL_IMMUNITY.name": "Odolnost vůči kouzlům",
 	"core.bonus.SPELL_IMMUNITY.description": "Odolnost proti ${subtype.spell}",
-	"core.bonus.SPELL_LIKE_ATTACK.name": "Spell-like attack",
-	"core.bonus.SPELL_LIKE_ATTACK.description": "Attacks with ${subtype.spell}",
-	"core.bonus.SPELL_RESISTANCE_AURA.name": "Aura of Resistance",
-	"core.bonus.SPELL_RESISTANCE_AURA.description": "Nearby stacks get ${val}% magic resistance",
-	"core.bonus.SUMMON_GUARDIANS.name": "Summon guardians",
-	"core.bonus.SUMMON_GUARDIANS.description": "At the start of battle summons ${subtype.creature} (${val}%)",
-	"core.bonus.SYNERGY_TARGET.name": "Synergizable",
+	"core.bonus.SPELL_LIKE_ATTACK.name": "Útok kouzlem",
+	"core.bonus.SPELL_LIKE_ATTACK.description": "Útočí kouzlem ${subtype.spell}",
+	"core.bonus.SPELL_RESISTANCE_AURA.name": "Aura odolnosti",
+	"core.bonus.SPELL_RESISTANCE_AURA.description": "Oddíly poblíž získají ${val}% magickou odolnost",
+	"core.bonus.SUMMON_GUARDIANS.name": "Povolat strážce",
+	"core.bonus.SUMMON_GUARDIANS.description": "Na začátku bitvy povolá ${subtype.creature} (${val}%)",
+	"core.bonus.SYNERGY_TARGET.name": "Synergizable", // TODO
 	"core.bonus.SYNERGY_TARGET.description": "This creature is vulnerable to synergy effect",
-	"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Breath",
-	"core.bonus.TWO_HEX_ATTACK_BREATH.description": "Breath Attack (2-hex range)",
-	"core.bonus.THREE_HEADED_ATTACK.name": "Three-headed attack",
-	"core.bonus.THREE_HEADED_ATTACK.description": "Attacks three adjacent units",
-	"core.bonus.TRANSMUTATION.name": "Transmutation",
-	"core.bonus.TRANSMUTATION.description": "${val}% chance to transform attacked unit to a different type",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Dech",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.description": "Dechový útok (dosah do dvou polí)",
+	"core.bonus.THREE_HEADED_ATTACK.name": "Tříhlavý útok",
+	"core.bonus.THREE_HEADED_ATTACK.description": "Útočí na tři sousední jednotky",
+	"core.bonus.TRANSMUTATION.name": "Transmutace",
+	"core.bonus.TRANSMUTATION.description": "${val}% šance na přeměnu útočené jednotky na jiný druh",
 	"core.bonus.UNDEAD.name": "Nemrtvý",
 	"core.bonus.UNDEAD.description": "Jednotka je nemrtvá",
-	"core.bonus.UNLIMITED_RETALIATIONS.name": "Unlimited retaliations",
-	"core.bonus.UNLIMITED_RETALIATIONS.description": "Can retaliate against an unlimited number of attacks",
-	"core.bonus.WATER_IMMUNITY.name": "Water immunity",
-	"core.bonus.WATER_IMMUNITY.description": "Immune to all spells from the school of Water magic",
-	"core.bonus.WIDE_BREATH.name": "Wide breath",
-	"core.bonus.WIDE_BREATH.description": "Wide breath attack (multiple hexes)"
+	"core.bonus.UNLIMITED_RETALIATIONS.name": "Neomezené odvety",
+	"core.bonus.UNLIMITED_RETALIATIONS.description": "Může se mstít za neomezený počet útoků",
+	"core.bonus.WATER_IMMUNITY.name": "Vodní odolnost",
+	"core.bonus.WATER_IMMUNITY.description": "Imunní všem kouzlům školy vodní magie",
+	"core.bonus.WIDE_BREATH.name": "Široký dech",
+	"core.bonus.WIDE_BREATH.description": "Útočí širokým dechem (více polí)"
 }

+ 5 - 0
Mods/vcmi/config/vcmi/english.json

@@ -54,6 +54,8 @@
 	"vcmi.radialWheel.moveDown" : "Move down",
 	"vcmi.radialWheel.moveBottom" : "Move to bottom",
 
+	"vcmi.spellBook.search" : "search...",
+
 	"vcmi.mainMenu.serverConnecting" : "Connecting...",
 	"vcmi.mainMenu.serverAddressEnter" : "Enter address:",
 	"vcmi.mainMenu.serverConnectionFailed" : "Failed to connect",
@@ -69,6 +71,7 @@
 	"vcmi.lobby.noPreview" : "no preview",
 	"vcmi.lobby.noUnderground" : "no underground",
 
+	"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
 	"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
 	"vcmi.server.errors.modsToEnable"    : "{Following mods are required}",
 	"vcmi.server.errors.modsToDisable"   : "{Following mods must be disabled}",
@@ -133,6 +136,8 @@
 	"vcmi.adventureOptions.leftButtonDrag.help" : "{Left Click Drag Map}\n\nWhen enabled, moving mouse with left button pressed will drag adventure map view.",
 	"vcmi.adventureOptions.smoothDragging.hover" : "Smooth Map Dragging",
 	"vcmi.adventureOptions.smoothDragging.help" : "{Smooth Map Dragging}\n\nWhen enabled, map dragging has a modern run out effect.",
+	"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Skip fading effects",
+	"vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Skip fading effects}\n\nWhen enabled, Skips object fadeout and similar effects (resource collection, ship embark etc). Makes UI more reactive in some cases at the expense of aesthetics. Especially useful in PvP games. For maximum movement speed skipping is active regardless of this setting.",
 	"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed6.hover": "",

+ 2 - 0
Mods/vcmi/config/vcmi/german.json

@@ -54,6 +54,8 @@
 	"vcmi.radialWheel.moveDown" : "Nach unten bewegen",
 	"vcmi.radialWheel.moveBottom" : "Ganz nach unten bewegen",
 
+	"vcmi.spellBook.search" : "suchen...",
+
 	"vcmi.mainMenu.serverConnecting" : "Verbinde...",
 	"vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:",
 	"vcmi.mainMenu.serverConnectionFailed" : "Verbindung fehlgeschlagen",

+ 5 - 0
Mods/vcmi/config/vcmi/ukrainian.json

@@ -54,6 +54,8 @@
 	"vcmi.radialWheel.moveDown" : "Перемістити вниз",
 	"vcmi.radialWheel.moveBottom" : "Перемістити у кінець",
 
+	"vcmi.spellBook.search" : "шукати...",
+
 	"vcmi.mainMenu.serverConnecting" : "Підключення...",
 	"vcmi.mainMenu.serverAddressEnter" : "Вкажіть адресу:",
 	"vcmi.mainMenu.serverConnectionFailed" : "Помилка з'єднання",
@@ -69,6 +71,7 @@
 	"vcmi.lobby.noPreview" : "огляд недоступний",
 	"vcmi.lobby.noUnderground" : "немає підземелля",
 
+	"vcmi.client.errors.missingCampaigns" : "{Не вистачає файлів даних}\n\nФайли даних кампаній не знайдено! Можливо, ви використовуєте неповні або пошкоджені файли даних Heroes 3. Будь ласка, перевстановіть дані гри.",
 	"vcmi.server.errors.existingProcess" : "Працює інший процес vcmiserver, будь ласка, спочатку завершіть його",
 	"vcmi.server.errors.modsToEnable"    : "{Потрібні модифікації для завантаження гри}",
 	"vcmi.server.errors.modsToDisable"   : "{Модифікації що мають бути вимкнені}",
@@ -133,6 +136,8 @@
 	"vcmi.adventureOptions.leftButtonDrag.help" : "{Переміщення мапи лівою кнопкою}\n\nЯкщо увімкнено, переміщення миші з натиснутою лівою кнопкою буде перетягувати мапу пригод",
 	"vcmi.adventureOptions.smoothDragging.hover" : "Плавне перетягування мапи",
 	"vcmi.adventureOptions.smoothDragging.help" : "{Плавне перетягування мапи}\n\nЯкщо увімкнено, перетягування мапи має сучасний ефект завершення.",
+	"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Вимкнути ефекти зникнення",
+	"vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Вимкнути ефекти зникнення}\n\nЯкщо увімкнено, пропускає зникання об'єктів та подібні ефекти (збирання ресурсів, посадка на корабель тощо). У деяких випадках робить інтерфейс більш реактивним за рахунок естетики. Особливо корисно в PvP-іграх. При максимальній швидкості пересування цей параметр увімкнено завжди, незалежно від цього параметра.",
 	"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
 	"vcmi.adventureOptions.mapScrollSpeed6.hover": "",

+ 2 - 2
android/vcmi-app/build.gradle

@@ -10,8 +10,8 @@ android {
 		applicationId "is.xyz.vcmi"
 		minSdk 19
 		targetSdk 33
-		versionCode 1410
-		versionName "1.4.1"
+		versionCode 1421
+		versionName "1.4.2"
 		setProperty("archivesBaseName", "vcmi")
 	}
 

+ 0 - 8
android/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java

@@ -156,14 +156,6 @@ public class NativeMethods
         }
     }
 
-    @SuppressWarnings(Const.JNI_METHOD_SUPPRESS)
-    public static String getFormattedDateTime()
-    {
-        String currentDate = new SimpleDateFormat((new SimpleDateFormat()).toLocalizedPattern(), Locale.getDefault()).format(new Date());
-
-        return currentDate;
-    }
-
     private static void internalProgressDisplay(final boolean show)
     {
         final Context ctx = SDL.getContext();

+ 71 - 0
android/vcmi-app/src/main/res/values-cs/strings.xml

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="url_project_page" translatable="false">https://vcmi.eu</string>
+    <string name="url_project_repo" translatable="false">https://github.com/vcmi/vcmi</string>
+    <string name="url_launcher_repo" translatable="false">https://github.com/vcmi/vcmi-android</string>
+    <string name="url_launcher_privacy" translatable="false">https://github.com/vcmi/vcmi/blob/master/docs/players/Privacy_Policy.md</string>
+
+    <string name="app_name">VCMI</string>
+    <string name="server_name">Server VCMI</string>
+    <string name="launcher_title">Spouštěč VCMI</string>
+    <string name="launcher_btn_scale_title">Škálování herního rozlišení</string>
+    <string name="launcher_btn_scale_subtitle_unknown">Současné: neznámé</string>
+    <string name="launcher_btn_scale_subtitle">Současné: %1$d%%</string>
+    <string name="launcher_btn_start_title">Spustit VCMI</string>
+    <string name="launcher_btn_start_subtitle">Současná verze VCMI: %1$s</string>
+    <string name="launcher_btn_mods_title">Modifikace</string>
+    <string name="launcher_btn_mods_subtitle">Nainstalovat nové frakce, přeměty a bonusy</string>
+    <string name="launcher_btn_language_title">Jazyk</string>
+    <string name="launcher_btn_language_subtitle_unknown">Současný: neznámý</string>
+    <string name="launcher_btn_language_subtitle">Současný: %1$s</string>
+    <string name="launcher_btn_pointermode_title">Změnit režim ukazatele</string>
+    <string name="launcher_btn_pointermode_subtitle">Současný: %1$s</string>
+    <string name="launcher_btn_pointermulti_title">Násobitel rychlosti relativního ukazatele</string>
+    <string name="launcher_btn_pointermulti_subtitle">Současný: %1$s</string>
+    <string name="launcher_btn_sound_title">Hlasitost zvuků</string>
+    <string name="launcher_btn_music_title">Hlasitost hudby</string>
+    <string name="launcher_btn_adventure_ai">AI světa</string>
+    <string name="launcher_btn_adventure_ai_title">Změnit AI světa</string>
+    <string name="launcher_btn_import_title">Importovat data VCMI</string>
+    <string name="launcher_btn_import_description">Zkopírovat soubury VCMI do vestavěného úložiště. Můžete importovat starou složku dat vcmi z vydání 0.99 nebo soubory HOMM3</string>
+    <string name="launcher_btn_export_title">Exportovat data VCMI</string>
+    <string name="launcher_btn_export_description">Udělat kopii dat VCMI před odinstalací nebo pro synchronizaci s desktopovou verzí. Též můžete přímo přistoupit k interním datům.</string>
+    <string name="launcher_progress_copy">Kopírování %1$s</string>
+    <string name="launcher_version">Současná verze spouštěče: %1$s</string>
+    <string name="launcher_error_vcmi_data_root_failed">Nelze vytvořit datovou složku VCMI v %1$s.</string>
+    <string name="launcher_error_h3_data_missing">Nelze najít datovou složku v \'%1$s\'. Vložte do ní své datové soubory HoMM3 nebo použijte tlačítko níže. Možná budete muset také restartovat aplikaci.</string>
+    <string name="launcher_error_vcmi_data_internal_missing">Nelze najít nebo rozbalit data vcmi ze zdrojů aplikace. Zkuste přeinstalovat aplikaci.</string>
+    <string name="launcher_error_vcmi_data_internal_update">Nelze aktualizovat data vcmi ze zdrojů aplikace. Zkuste přeinstalovat aplikaci.</string>
+    <string name="launcher_error_permissions">Tato aplikace potřebuje oprávnění k zápisu pro použití obsahu na externím úložišti</string>
+    <string name="launcher_error_permission_broken">Nelze správně vyřešit oprávnění</string>
+    <string name="mods_item_author_template">od %1$s</string>
+    <string name="misc_try_again">Zkusit znovu</string>
+    <string name="launcher_section_init">Inicializae hry</string>
+    <string name="launcher_section_settings">Nastavení</string>
+    <string name="menu_mods_download_repo">Stáhnout data repozitáře</string>
+
+    <string name="misc_pointermode_normal">Normální</string>
+    <string name="misc_pointermode_relative">Relativní</string>
+    <string name="menu_launcher_about">O spouštěči</string>
+
+    <string name="mods_title">Nalezené modifikace</string>
+    <string name="mods_failed_mod_loading">Nelze načíst modifikaci ve složce \'%1$s\'</string>
+    <string name="mods_removal_title">Odebírání %1$s</string>
+    <string name="mods_removal_confirmation">Jste si jisti odebráním %1$s?</string>
+
+    <string name="about_title">O aplikaci</string>
+    <string name="about_version_app">Verze aplikace: %1$s</string>
+    <string name="about_version_launcher">Verze spouštěče: %1$s</string>
+    <string name="about_section_project">Projekt</string>
+    <string name="about_section_legal">Právní záležitosti</string>
+    <string name="about_links_main">Hlavní stránka: %1$s</string>
+    <string name="about_links_repo">Repozitář projektu: %1$s</string>
+    <string name="about_links_repo_launcher">Repozitář spouštěče: %1$s</string>
+    <string name="about_btn_authors">Autoři</string>
+    <string name="about_btn_privacy">Zásady ochrany osobních údajů: %1$s</string>
+    <string name="about_error_opening_url">Nebylo možné otevřít webovou stránku (nenalezena patřičná aplikace)</string>
+
+    <string name="dialog_authors_vcmi">Autoři VCMI</string>
+    <string name="dialog_authors_launcher">Autoři spouštěče</string>
+    <string name="launcher_error_config_saving_failed">Nelze uložit konfigurační soubor VCMI; důvod: %1$s</string>
+</resources>

+ 31 - 11
client/CMT.cpp

@@ -55,12 +55,14 @@
 namespace po = boost::program_options;
 namespace po_style = boost::program_options::command_line_style;
 
+static std::atomic<bool> quitRequestedDuringOpeningPlayback = false;
 static po::variables_map vm;
 
 #ifndef VCMI_IOS
 void processCommand(const std::string &message);
 #endif
 void playIntro();
+[[noreturn]] static void quitApplication();
 static void mainLoop();
 
 static CBasicLogConfigurator *logConfig;
@@ -257,10 +259,10 @@ int main(int argc, char * argv[])
 	};
 
 	testFile("DATA/HELP.TXT", "VCMI requires Heroes III: Shadow of Death or Heroes III: Complete data files to run!");
-	testFile("MODS/VCMI/MOD.JSON", "VCMI installation is corrupted! Built-in mod was not found!");
-	testFile("DATA/PLAYERS.PAL", "Heroes III data files are missing or corruped! Please reinstall them.");
-	testFile("SPRITES/DEFAULT.DEF", "Heroes III data files are missing or corruped! Please reinstall them.");
 	testFile("DATA/TENTCOLR.TXT", "Heroes III: Restoration of Erathia (including HD Edition) data files are not supported!");
+	testFile("MODS/VCMI/MOD.JSON", "VCMI installation is corrupted! Built-in mod was not found!");
+	testFile("DATA/PLAYERS.PAL", "Heroes III data files (Data/H3Bitmap.lod) are incomplete or corruped! Please reinstall them.");
+	testFile("SPRITES/DEFAULT.DEF", "Heroes III data files (Data/H3Sprite.lod) are incomplete or corruped! Please reinstall them.");
 
 	srand ( (unsigned int)time(nullptr) );
 
@@ -313,7 +315,6 @@ int main(int argc, char * argv[])
 		GH.screenHandler().clearScreen();
 	}
 
-
 #ifndef VCMI_NO_THREADED_LOAD
 	#ifdef VCMI_ANDROID // android loads the data quite slowly so we display native progressbar to prevent having only black screen for few seconds
 	{
@@ -327,6 +328,9 @@ int main(int argc, char * argv[])
 	#endif // ANDROID
 #endif // THREADED
 
+	if (quitRequestedDuringOpeningPlayback)
+		quitApplication();
+
 	if(!settings["session"]["headless"].Bool())
 	{
 		pomtime.getDiff();
@@ -414,7 +418,7 @@ int main(int argc, char * argv[])
 	else
 	{
 		while(true)
-			boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
+			boost::this_thread::sleep_for(boost::chrono::milliseconds(200));
 	}
 
 	return 0;
@@ -451,7 +455,7 @@ static void mainLoop()
 	}
 }
 
-static void quitApplication()
+[[noreturn]] static void quitApplication()
 {
 	if(!settings["session"]["headless"].Bool())
 	{
@@ -487,7 +491,8 @@ static void quitApplication()
 	vstd::clear_pointer(CSH);
 	vstd::clear_pointer(VLC);
 
-	vstd::clear_pointer(console);// should be removed after everything else since used by logging
+	// sometimes leads to a hang. TODO: investigate
+	//vstd::clear_pointer(console);// should be removed after everything else since used by logging
 
 	if(!settings["session"]["headless"].Bool())
 		GH.screenHandler().close();
@@ -501,14 +506,29 @@ static void quitApplication()
 
 	std::cout << "Ending...\n";
 
-	// this method is always called from event/network threads, which keep interface mutex locked
-	// unlock it here to avoid assertion failure on GH destruction in exit()
-	GH.interfaceMutex.unlock();
-	exit(0);
+	// Perform quick exit without executing static destructors and let OS cleanup anything that we did not
+	// We generally don't care about them and this leads to numerous issues, e.g.
+	// destruction of locked mutexes (fails an assertion), even in third-party libraries (as well as native libs on Android)
+	// Android - std::quick_exit is available only starting from API level 21
+	// Mingw, macOS and iOS - std::quick_exit is unavailable (at least in current version of CI)
+#if (defined(__ANDROID_API__) && __ANDROID_API__ < 21) || (defined(__MINGW32__)) || defined(VCMI_APPLE)
+	::exit(0);
+#else
+	std::quick_exit(0);
+#endif
 }
 
 void handleQuit(bool ask)
 {
+	// FIXME: avoids crash if player attempts to close game while opening is still playing
+	// use cursor handler as indicator that loading is not done yet
+	// proper solution would be to abort init thread (or wait for it to finish)
+	if (!CCS->curh)
+	{
+		quitRequestedDuringOpeningPlayback = true;
+		return;
+	}
+
 	if(ask)
 	{
 		CCS->curh->set(Cursor::Map::POINTER);

+ 1 - 1
client/CVideoHandler.cpp

@@ -637,7 +637,7 @@ bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey)
 
 		SDL_Rect rect = CSDL_Ext::toSDL(pos);
 
-		SDL_RenderClear(mainRenderer);
+		SDL_RenderFillRect(mainRenderer, &rect);
 		SDL_RenderCopy(mainRenderer, texture, nullptr, &rect);
 		SDL_RenderPresent(mainRenderer);
 

+ 5 - 1
client/NetPacksLobbyClient.cpp

@@ -15,6 +15,7 @@
 
 #include "lobby/OptionsTab.h"
 #include "lobby/RandomMapTab.h"
+#include "lobby/TurnOptionsTab.h"
 #include "lobby/SelectionTab.h"
 #include "lobby/CBonusSelection.h"
 
@@ -47,7 +48,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon
 
 void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)
 {
-	if(pack.clientId != pack.c->connectionID)
+	if(pack.clientId != handler.c->connectionID)
 	{
 		result = false;
 		return;
@@ -95,6 +96,9 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack
 	case LobbyGuiAction::OPEN_RANDOM_MAP_OPTIONS:
 		lobby->toggleTab(lobby->tabRand);
 		break;
+	case LobbyGuiAction::OPEN_TURN_OPTIONS:
+		lobby->toggleTab(lobby->tabTurnOptions);
+		break;
 	}
 }
 

+ 8 - 6
client/PlayerLocalState.cpp

@@ -204,7 +204,7 @@ const CGHeroInstance * PlayerLocalState::getWanderingHero(size_t index)
 {
 	if(index < wanderingHeroes.size())
 		return wanderingHeroes[index];
-	return nullptr;
+	throw std::runtime_error("No hero with index " + std::to_string(index));
 }
 
 void PlayerLocalState::addWanderingHero(const CGHeroInstance * hero)
@@ -235,10 +235,12 @@ void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero)
 		setSelection(ownedTowns.front());
 }
 
-void PlayerLocalState::swapWanderingHero(int pos1, int pos2)
+void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2)
 {
 	assert(wanderingHeroes[pos1] && wanderingHeroes[pos2]);
-	std::swap(wanderingHeroes[pos1], wanderingHeroes[pos2]);
+	std::swap(wanderingHeroes.at(pos1), wanderingHeroes.at(pos2));
+
+	adventureInt->onHeroOrderChanged();
 }
 
 const std::vector<const CGTownInstance *> & PlayerLocalState::getOwnedTowns()
@@ -250,7 +252,7 @@ const CGTownInstance * PlayerLocalState::getOwnedTown(size_t index)
 {
 	if(index < ownedTowns.size())
 		return ownedTowns[index];
-	return nullptr;
+	throw std::runtime_error("No town with index " + std::to_string(index));
 }
 
 void PlayerLocalState::addOwnedTown(const CGTownInstance * town)
@@ -276,10 +278,10 @@ void PlayerLocalState::removeOwnedTown(const CGTownInstance * town)
 		setSelection(ownedTowns.front());
 }
 
-void PlayerLocalState::swapOwnedTowns(int pos1, int pos2)
+void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2)
 {
 	assert(ownedTowns[pos1] && ownedTowns[pos2]);
-	std::swap(ownedTowns[pos1], ownedTowns[pos2]);
+	std::swap(ownedTowns.at(pos1), ownedTowns.at(pos2));
 
 	adventureInt->onTownOrderChanged();
 }

+ 4 - 4
client/PlayerLocalState.h

@@ -42,7 +42,7 @@ public:
 	{
 		//on which page we left spellbook
 		int spellbookLastPageBattle = 0;
-		int spellbokLastPageAdvmap = 0;
+		int spellbookLastPageAdvmap = 0;
 		int spellbookLastTabBattle = 4;
 		int spellbookLastTabAdvmap = 4;
 
@@ -50,7 +50,7 @@ public:
 		void serialize(Handler & h, const int version)
 		{
 			h & spellbookLastPageBattle;
-			h & spellbokLastPageAdvmap;
+			h & spellbookLastPageAdvmap;
 			h & spellbookLastTabBattle;
 			h & spellbookLastTabAdvmap;
 		}
@@ -66,14 +66,14 @@ public:
 	const CGTownInstance * getOwnedTown(size_t index);
 	void addOwnedTown(const CGTownInstance * hero);
 	void removeOwnedTown(const CGTownInstance * hero);
-	void swapOwnedTowns(int pos1, int pos2);
+	void swapOwnedTowns(size_t pos1, size_t pos2);
 
 	const std::vector<const CGHeroInstance *> & getWanderingHeroes();
 	const CGHeroInstance * getWanderingHero(size_t index);
 	const CGHeroInstance * getNextWanderingHero(const CGHeroInstance * hero);
 	void addWanderingHero(const CGHeroInstance * hero);
 	void removeWanderingHero(const CGHeroInstance * hero);
-	void swapWanderingHero(int pos1, int pos2);
+	void swapWanderingHero(size_t pos1, size_t pos2);
 
 	void setPath(const CGHeroInstance * h, const CGPath & path);
 	bool setPath(const CGHeroInstance * h, const int3 & destination);

+ 7 - 2
client/adventureMap/AdventureMapInterface.cpp

@@ -65,8 +65,8 @@ AdventureMapInterface::AdventureMapInterface():
 	shortcuts->setState(EAdventureState::MAKING_TURN);
 	widget->getMapView()->onViewMapActivated();
 
-	if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.isEnabled() || LOCPLINT->cb->getStartInfo()->turnTimerInfo.isBattleEnabled())
-		watches = std::make_shared<TurnTimerWidget>();
+	if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.turnTimer != 0)
+		watches = std::make_shared<TurnTimerWidget>(Point(24, 24));
 	
 	addUsedEvents(KEYBOARD | TIME);
 }
@@ -331,6 +331,11 @@ void AdventureMapInterface::onTownOrderChanged()
 	widget->getTownList()->updateWidget();
 }
 
+void AdventureMapInterface::onHeroOrderChanged()
+{
+	widget->getHeroList()->updateWidget();
+}
+
 void AdventureMapInterface::onMapTilesChanged(boost::optional<std::unordered_set<int3>> positions)
 {
 	if (positions)

+ 3 - 0
client/adventureMap/AdventureMapInterface.h

@@ -149,6 +149,9 @@ public:
 	/// Called when town order changes
 	void onTownOrderChanged();
 
+	/// Called when hero order changes
+	void onHeroOrderChanged();
+
 	/// Called when map audio should be paused, e.g. on combat or town screen access
 	void onAudioPaused();
 

+ 3 - 0
client/adventureMap/CInGameConsole.cpp

@@ -151,6 +151,9 @@ void CInGameConsole::keyPressed (EShortcut key)
 		break;
 
 	case EShortcut::GAME_ACTIVATE_CONSOLE:
+		if(GH.isKeyboardAltDown())
+			return; //QoL for alt-tab operating system shortcut
+
 		if(!enteredText.empty())
 			endEnteringText(false);
 		else

+ 31 - 31
client/adventureMap/CList.cpp

@@ -218,8 +218,7 @@ CHeroList::CEmptyHeroItem::CEmptyHeroItem()
 
 CHeroList::CHeroItem::CHeroItem(CHeroList *parent, const CGHeroInstance * Hero)
 	: CListItem(parent),
-	hero(Hero),
-	parentList(parent)
+	hero(Hero)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	movement = std::make_shared<CAnimImage>(AnimationPath::builtin("IMOBIL"), 0, 0, 0, 1);
@@ -280,24 +279,22 @@ void CHeroList::CHeroItem::gesture(bool on, const Point & initialPosition, const
 	if(heroes.size() < 2)
 		return;
 
-	int heroPos = vstd::find_pos(heroes, hero);
-	const CGHeroInstance * heroUpper = (heroPos < 1) ? nullptr : heroes[heroPos - 1];
-	const CGHeroInstance * heroLower = (heroPos > heroes.size() - 2) ? nullptr : heroes[heroPos + 1];
+	size_t heroPos = vstd::find_pos(heroes, hero);
+	const CGHeroInstance * heroUpper = (heroPos < 1) ? nullptr : heroes.at(heroPos - 1);
+	const CGHeroInstance * heroLower = (heroPos > heroes.size() - 2) ? nullptr : heroes.at(heroPos + 1);
 
 	std::vector<RadialMenuConfig> menuElements = {
-		{ RadialMenuConfig::ITEM_ALT_NN, heroUpper != nullptr, "altUpTop", "vcmi.radialWheel.moveTop", [this, heroPos]()
+		{ RadialMenuConfig::ITEM_ALT_NN, heroUpper != nullptr, "altUpTop", "vcmi.radialWheel.moveTop", [heroPos]()
 		{
-			for (int i = heroPos; i > 0; i--)
+			for (size_t i = heroPos; i > 0; i--)
 				LOCPLINT->localState->swapWanderingHero(i, i - 1);
-			parentList->updateWidget();
 		} },
-		{ RadialMenuConfig::ITEM_ALT_NW, heroUpper != nullptr, "altUp", "vcmi.radialWheel.moveUp", [this, heroPos](){LOCPLINT->localState->swapWanderingHero(heroPos, heroPos - 1); parentList->updateWidget(); } },
-		{ RadialMenuConfig::ITEM_ALT_SW, heroLower != nullptr, "altDown", "vcmi.radialWheel.moveDown", [this, heroPos](){ LOCPLINT->localState->swapWanderingHero(heroPos, heroPos + 1); parentList->updateWidget(); } },
-		{ RadialMenuConfig::ITEM_ALT_SS, heroLower != nullptr, "altDownBottom", "vcmi.radialWheel.moveBottom", [this, heroPos, heroes]()
+		{ RadialMenuConfig::ITEM_ALT_NW, heroUpper != nullptr, "altUp", "vcmi.radialWheel.moveUp", [heroPos](){LOCPLINT->localState->swapWanderingHero(heroPos, heroPos - 1); } },
+		{ RadialMenuConfig::ITEM_ALT_SW, heroLower != nullptr, "altDown", "vcmi.radialWheel.moveDown", [heroPos](){ LOCPLINT->localState->swapWanderingHero(heroPos, heroPos + 1); } },
+		{ RadialMenuConfig::ITEM_ALT_SS, heroLower != nullptr, "altDownBottom", "vcmi.radialWheel.moveBottom", [heroPos, heroes]()
 		{
 			for (int i = heroPos; i < heroes.size() - 1; i++)
 				LOCPLINT->localState->swapWanderingHero(i, i + 1);
-			parentList->updateWidget();
 		} },
 	};
 
@@ -329,7 +326,7 @@ void CHeroList::updateElement(const CGHeroInstance * hero)
 
 void CHeroList::updateWidget()
 {
-	auto & heroes = LOCPLINT->localState->getWanderingHeroes();
+	const auto & heroes = LOCPLINT->localState->getWanderingHeroes();
 
 	listBox->resize(heroes.size());
 
@@ -340,7 +337,7 @@ void CHeroList::updateWidget()
 		if (!item)
 			continue;
 
-		if (item->hero == heroes[i])
+		if (item->hero == heroes.at(i))
 		{
 			item->update();
 		}
@@ -366,11 +363,8 @@ std::shared_ptr<CIntObject> CTownList::createItem(size_t index)
 
 CTownList::CTownItem::CTownItem(CTownList *parent, const CGTownInstance *Town):
 	CListItem(parent),
-	parentList(parent)
+	town(Town)
 {
-	const std::vector<const CGTownInstance *> towns = LOCPLINT->localState->getOwnedTowns();
-	townIndex = std::distance(towns.begin(), std::find(towns.begin(), towns.end(), Town));
-
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	picture = std::make_shared<CAnimImage>(AnimationPath::builtin("ITPA"), 0);
 	pos = picture->pos;
@@ -386,7 +380,6 @@ std::shared_ptr<CIntObject> CTownList::CTownItem::genSelection()
 
 void CTownList::CTownItem::update()
 {
-	const CGTownInstance * town = LOCPLINT->localState->getOwnedTowns()[townIndex];
 	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 	picture->setFrame(iconIndex + 2);
@@ -396,17 +389,17 @@ void CTownList::CTownItem::update()
 void CTownList::CTownItem::select(bool on)
 {
 	if(on)
-		LOCPLINT->localState->setSelection(LOCPLINT->localState->getOwnedTowns()[townIndex]);
+		LOCPLINT->localState->setSelection(town);
 }
 
 void CTownList::CTownItem::open()
 {
-	LOCPLINT->openTownWindow(LOCPLINT->localState->getOwnedTowns()[townIndex]);
+	LOCPLINT->openTownWindow(town);
 }
 
 void CTownList::CTownItem::showTooltip()
 {
-	CRClickPopup::createAndPush(LOCPLINT->localState->getOwnedTowns()[townIndex], GH.getCursorPosition());
+	CRClickPopup::createAndPush(town, GH.getCursorPosition());
 }
 
 void CTownList::CTownItem::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
@@ -415,8 +408,9 @@ void CTownList::CTownItem::gesture(bool on, const Point & initialPosition, const
 		return;
 
 	const std::vector<const CGTownInstance *> towns = LOCPLINT->localState->getOwnedTowns();
+	size_t townIndex = vstd::find_pos(towns, town);
 
-	if(townIndex < 0 || townIndex > towns.size() - 1 || !towns[townIndex])
+	if(townIndex + 1 > towns.size() || !towns.at(townIndex))
 		return;
 
 	if(towns.size() < 2)
@@ -426,19 +420,17 @@ void CTownList::CTownItem::gesture(bool on, const Point & initialPosition, const
 	int townLowerPos = (townIndex > towns.size() - 2) ? -1 : townIndex + 1;
 
 	std::vector<RadialMenuConfig> menuElements = {
-		{ RadialMenuConfig::ITEM_ALT_NN, townUpperPos > -1, "altUpTop", "vcmi.radialWheel.moveTop", [this]()
+		{ RadialMenuConfig::ITEM_ALT_NN, townUpperPos > -1, "altUpTop", "vcmi.radialWheel.moveTop", [townIndex]()
 		{
 			for (int i = townIndex; i > 0; i--)
 				LOCPLINT->localState->swapOwnedTowns(i, i - 1);
-			parentList->updateWidget();
 		} },
-		{ RadialMenuConfig::ITEM_ALT_NW, townUpperPos > -1, "altUp", "vcmi.radialWheel.moveUp", [this, townUpperPos](){LOCPLINT->localState->swapOwnedTowns(townIndex, townUpperPos); parentList->updateWidget(); } },
-		{ RadialMenuConfig::ITEM_ALT_SW, townLowerPos > -1, "altDown", "vcmi.radialWheel.moveDown", [this, townLowerPos](){ LOCPLINT->localState->swapOwnedTowns(townIndex, townLowerPos); parentList->updateWidget(); } },
-		{ RadialMenuConfig::ITEM_ALT_SS, townLowerPos > -1, "altDownBottom", "vcmi.radialWheel.moveBottom", [this, towns]()
+		{ RadialMenuConfig::ITEM_ALT_NW, townUpperPos > -1, "altUp", "vcmi.radialWheel.moveUp", [townIndex, townUpperPos](){LOCPLINT->localState->swapOwnedTowns(townIndex, townUpperPos); } },
+		{ RadialMenuConfig::ITEM_ALT_SW, townLowerPos > -1, "altDown", "vcmi.radialWheel.moveDown", [townIndex, townLowerPos](){ LOCPLINT->localState->swapOwnedTowns(townIndex, townLowerPos); } },
+		{ RadialMenuConfig::ITEM_ALT_SS, townLowerPos > -1, "altDownBottom", "vcmi.radialWheel.moveBottom", [townIndex, towns]()
 		{
 			for (int i = townIndex; i < towns.size() - 1; i++)
 				LOCPLINT->localState->swapOwnedTowns(i, i + 1);
-			parentList->updateWidget();
 		} },
 	};
 
@@ -447,7 +439,7 @@ void CTownList::CTownItem::gesture(bool on, const Point & initialPosition, const
 
 std::string CTownList::CTownItem::getHoverText()
 {
-	return LOCPLINT->localState->getOwnedTowns()[townIndex]->getObjectName();
+	return town->getObjectName();
 }
 
 CTownList::CTownList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount)
@@ -479,7 +471,15 @@ void CTownList::updateWidget()
 		if (!item)
 			continue;
 
-		listBox->reset();
+		if (item->town == towns[i])
+		{
+			item->update();
+		}
+		else
+		{
+			listBox->reset();
+			break;
+		}
 	}
 
 	if (LOCPLINT->localState->getCurrentTown())

+ 1 - 3
client/adventureMap/CList.h

@@ -117,7 +117,6 @@ class CHeroList	: public CList
 		std::shared_ptr<CAnimImage> movement;
 		std::shared_ptr<CAnimImage> mana;
 		std::shared_ptr<CAnimImage> portrait;
-		CHeroList *parentList;
 	public:
 		const CGHeroInstance * const hero;
 
@@ -152,9 +151,8 @@ class CTownList	: public CList
 	class CTownItem : public CListItem
 	{
 		std::shared_ptr<CAnimImage> picture;
-		CTownList *parentList;
 	public:
-		int townIndex;
+		const CGTownInstance * const town;
 
 		CTownItem(CTownList *parent, const CGTownInstance * town);
 

+ 140 - 111
client/adventureMap/TurnTimerWidget.cpp

@@ -15,54 +15,86 @@
 #include "../CPlayerInterface.h"
 #include "../battle/BattleInterface.h"
 #include "../battle/BattleStacksController.h"
-
-#include "../render/EFont.h"
-#include "../render/Graphics.h"
 #include "../gui/CGuiHandler.h"
-#include "../gui/TextAlignment.h"
+#include "../render/Graphics.h"
 #include "../widgets/Images.h"
+#include "../widgets/MiscWidgets.h"
 #include "../widgets/TextControls.h"
+
 #include "../../CCallback.h"
-#include "../../lib/CStack.h"
 #include "../../lib/CPlayerState.h"
-#include "../../lib/filesystem/ResourcePath.h"
+#include "../../lib/CStack.h"
+#include "../../lib/StartInfo.h"
 
-TurnTimerWidget::DrawRect::DrawRect(const Rect & r, const ColorRGBA & c):
-	CIntObject(), rect(r), color(c)
-{
-}
+TurnTimerWidget::TurnTimerWidget(const Point & position)
+	: TurnTimerWidget(position, PlayerColor::NEUTRAL)
+{}
 
-void TurnTimerWidget::DrawRect::showAll(Canvas & to)
+TurnTimerWidget::TurnTimerWidget(const Point & position, PlayerColor player)
+	: CIntObject(TIME)
+	, lastSoundCheckSeconds(0)
+	, isBattleMode(player.isValidPlayer())
 {
-	to.drawColor(rect, color);
-	
-	CIntObject::showAll(to);
-}
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
-TurnTimerWidget::TurnTimerWidget():
-	InterfaceObjectConfigurable(TIME),
-	turnTime(0), lastTurnTime(0), cachedTurnTime(0), lastPlayer(PlayerColor::CANNOT_DETERMINE)
-{
-	REGISTER_BUILDER("drawRect", &TurnTimerWidget::buildDrawRect);
-	
+	pos += position;
+	pos.w = 0;
+	pos.h = 0;
 	recActions &= ~DEACTIVATE;
-	
-	const JsonNode config(JsonPath::builtin("config/widgets/turnTimer.json"));
-	
-	build(config);
-	
-	std::transform(variables["notificationTime"].Vector().begin(),
-				   variables["notificationTime"].Vector().end(),
-				   std::inserter(notifications, notifications.begin()),
-				   [](const JsonNode & node){ return node.Integer(); });
-}
+	const auto & timers = LOCPLINT->cb->getStartInfo()->turnTimerInfo;
 
-std::shared_ptr<TurnTimerWidget::DrawRect> TurnTimerWidget::buildDrawRect(const JsonNode & config) const
-{
-	logGlobal->debug("Building widget TurnTimerWidget::DrawRect");
-	auto rect = readRect(config["rect"]);
-	auto color = readColor(config["color"]);
-	return std::make_shared<TurnTimerWidget::DrawRect>(rect, color);
+	backgroundTexture = std::make_shared<CFilledTexture>(ImagePath::builtin("DiBoxBck"), pos); // 1 px smaller on all sides
+
+	if (isBattleMode)
+		backgroundBorder = std::make_shared<TransparentFilledRectangle>(pos, ColorRGBA(0, 0, 0, 128), Colors::BRIGHT_YELLOW);
+	else
+		backgroundBorder = std::make_shared<TransparentFilledRectangle>(pos, ColorRGBA(0, 0, 0, 128), Colors::BLACK);
+
+	if (isBattleMode)
+	{
+		pos.w = 76;
+
+		pos.h += 20;
+		playerLabelsMain[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 10, FONT_BIG, ETextAlignment::CENTER, graphics->playerColors[player], "");
+
+		if (timers.battleTimer != 0)
+		{
+			pos.h += 20;
+			playerLabelsBattle[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 10, FONT_BIG, ETextAlignment::CENTER, graphics->playerColors[player], "");
+		}
+
+		if (!timers.accumulatingUnitTimer && timers.unitTimer != 0)
+		{
+			pos.h += 20;
+			playerLabelsUnit[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 10, FONT_BIG, ETextAlignment::CENTER, graphics->playerColors[player], "");
+		}
+
+		updateTextLabel(player, LOCPLINT->cb->getPlayerTurnTime(player));
+	}
+	else
+	{
+		if (!timers.accumulatingTurnTimer && timers.baseTimer != 0)
+			pos.w = 120;
+		else
+			pos.w = 60;
+
+		for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player)
+		{
+			if (LOCPLINT->cb->getStartInfo()->playerInfos.count(player) == 0)
+				continue;
+
+			if (!LOCPLINT->cb->getStartInfo()->playerInfos.at(player).isControlledByHuman())
+				continue;
+
+			pos.h += 20;
+			playerLabelsMain[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 10, FONT_BIG, ETextAlignment::CENTER, graphics->playerColors[player], "");
+
+			updateTextLabel(player, LOCPLINT->cb->getPlayerTurnTime(player));
+		}
+	}
+
+	backgroundTexture->pos = Rect::createAround(pos, -1);
+	backgroundBorder->pos = pos;
 }
 
 void TurnTimerWidget::show(Canvas & to)
@@ -70,98 +102,95 @@ void TurnTimerWidget::show(Canvas & to)
 	showAll(to);
 }
 
-void TurnTimerWidget::setTime(PlayerColor player, int time)
+void TurnTimerWidget::updateNotifications(PlayerColor player, int timeMs)
 {
-	int newTime = time / 1000;
-	if(player == LOCPLINT->playerID
-	   && newTime != turnTime
-	   && notifications.count(newTime))
-	{
-		CCS->soundh->playSound(AudioPath::fromJson(variables["notificationSound"]));
-	}
+	if(player != LOCPLINT->playerID)
+		return;
 
-	turnTime = newTime;
+	int newTimeSeconds = timeMs / 1000;
 
-	if(auto w = widget<CLabel>("timer"))
-	{
-		std::ostringstream oss;
-		oss << turnTime / 60 << ":" << std::setw(2) << std::setfill('0') << turnTime % 60;
-		w->setText(oss.str());
-		
-		if(graphics && LOCPLINT && LOCPLINT->cb
-		   && variables["textColorFromPlayerColor"].Bool()
-		   && player.isValidPlayer())
-		{
-			w->setColor(graphics->playerColors[player]);
-		}
-	}
+	if (newTimeSeconds != lastSoundCheckSeconds && notificationThresholds.count(newTimeSeconds))
+		CCS->soundh->playSound(AudioPath::builtin("WE5"));
+
+	lastSoundCheckSeconds = newTimeSeconds;
 }
 
-void TurnTimerWidget::updateTimer(PlayerColor player, uint32_t msPassed)
+static std::string msToString(int timeMs)
 {
-	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)
+	int timeSeconds = timeMs / 1000;
+	std::ostringstream oss;
+	oss << timeSeconds / 60 << ":" << std::setw(2) << std::setfill('0') << timeSeconds % 60;
+	return oss.str();
+}
+
+void TurnTimerWidget::updateTextLabel(PlayerColor player, const TurnTimerInfo & timer)
+{
+	const auto & timerSettings = LOCPLINT->cb->getStartInfo()->turnTimerInfo;
+	auto mainLabel = playerLabelsMain[player];
+
+	if (isBattleMode)
 	{
-		if(time / 1000 != lastTurnTime / 1000)
+		mainLabel->setText(msToString(timer.baseTimer + timer.turnTimer));
+
+		if (timerSettings.battleTimer != 0)
 		{
-			//do not update timer on this tick
-			lastTurnTime = time;
-			cachedTurnTime = time;
+			auto battleLabel = playerLabelsBattle[player];
+			if (timer.battleTimer != 0)
+			{
+				if (timerSettings.accumulatingUnitTimer)
+					battleLabel->setText("+" + msToString(timer.battleTimer + timer.unitTimer));
+				else
+					battleLabel->setText("+" + msToString(timer.battleTimer));
+			}
+			else
+				battleLabel->setText("");
 		}
-		else
-			setTime(player, cachedTurnTime);
-	};
-	
-	auto * playerInfo = LOCPLINT->cb->getPlayer(player);
-	if(player.isValidPlayer() || (playerInfo && playerInfo->isHuman()))
+
+		if (!timerSettings.accumulatingUnitTimer && timerSettings.unitTimer != 0)
+		{
+			auto unitLabel = playerLabelsUnit[player];
+			if (timer.unitTimer != 0)
+				unitLabel->setText("+" + msToString(timer.unitTimer));
+			else
+				unitLabel->setText("");
+		}
+	}
+	else
 	{
-		if(time.isBattle)
-			timeCheckAndUpdate(time.baseTimer + time.turnTimer + time.battleTimer + time.unitTimer);
+		if (!timerSettings.accumulatingTurnTimer && timerSettings.baseTimer != 0)
+			mainLabel->setText(msToString(timer.baseTimer) + "+" + msToString(timer.turnTimer));
 		else
-			timeCheckAndUpdate(time.baseTimer + time.turnTimer);
+			mainLabel->setText(msToString(timer.baseTimer + timer.turnTimer));
 	}
-	else
-		timeCheckAndUpdate(0);
 }
 
-void TurnTimerWidget::tick(uint32_t msPassed)
+void TurnTimerWidget::updateTimer(PlayerColor player, uint32_t msPassed)
 {
-	if(!LOCPLINT || !LOCPLINT->cb)
-		return;
+	const auto & gamestateTimer = LOCPLINT->cb->getPlayerTurnTime(player);
+	updateNotifications(player, gamestateTimer.valueMs());
+	updateTextLabel(player, gamestateTimer);
+}
 
-	if(LOCPLINT->battleInt)
-	{
-		if(auto * stack = LOCPLINT->battleInt->stacksController->getActiveStack())
-			updateTimer(stack->getOwner(), msPassed);
-		else
-			updateTimer(PlayerColor::NEUTRAL, msPassed);
-	}
-	else
+void TurnTimerWidget::tick(uint32_t msPassed)
+{
+	for(const auto & player : playerLabelsMain)
 	{
-		if(LOCPLINT->makingTurn)
-			updateTimer(LOCPLINT->playerID, msPassed);
-		else
+		if (LOCPLINT->battleInt)
 		{
-			for(PlayerColor p(0); p < PlayerColor::PLAYER_LIMIT; ++p)
-			{
-				if(LOCPLINT->cb->isPlayerMakingTurn(p))
-				{
-					updateTimer(p, msPassed);
-					break;
-				}
-			}
+			const auto & battle = LOCPLINT->battleInt->getBattle();
+
+			bool isDefender = battle->sideToPlayer(BattleSide::DEFENDER) == player.first;
+			bool isAttacker = battle->sideToPlayer(BattleSide::ATTACKER) == player.first;
+			bool isMakingUnitTurn = battle->battleActiveUnit() && battle->battleActiveUnit()->unitOwner() == player.first;
+			bool isEngagedInBattle = isDefender || isAttacker;
+
+			// Due to way our network message queue works during battle animation
+			// client actually does not receives updates from server as to which timer is active when game has battle animations playing
+			// so during battle skip updating timer unless game is waiting for player to select action
+			if (isEngagedInBattle && !isMakingUnitTurn)
+				continue;
 		}
+
+		updateTimer(player.first, msPassed);
 	}
 }

+ 23 - 30
client/adventureMap/TurnTimerWidget.h

@@ -11,50 +11,43 @@
 #pragma once
 
 #include "../gui/CIntObject.h"
-#include "../gui/InterfaceObjectConfigurable.h"
-#include "../render/Canvas.h"
 #include "../render/Colors.h"
+#include "../../lib/TurnTimerInfo.h"
 
 class CAnimImage;
 class CLabel;
+class CFilledTexture;
+class TransparentFilledRectangle;
 
 VCMI_LIB_NAMESPACE_BEGIN
-
 class PlayerColor;
-
 VCMI_LIB_NAMESPACE_END
 
-class TurnTimerWidget : public InterfaceObjectConfigurable
+class TurnTimerWidget : public CIntObject
 {
-private:
-	
-	class DrawRect : public CIntObject
-	{
-		const Rect rect;
-		const ColorRGBA color;
-		
-	public:
-		DrawRect(const Rect &, const ColorRGBA &);
-		void showAll(Canvas & to) override;
-	};
-
-	int turnTime;
-	int lastTurnTime;
-	int cachedTurnTime;
-	PlayerColor lastPlayer;
-	
-	std::set<int> notifications;
-	
-	std::shared_ptr<DrawRect> buildDrawRect(const JsonNode & config) const;
-	
-	void updateTimer(PlayerColor player, uint32_t msPassed);
+	int lastSoundCheckSeconds;
+	bool isBattleMode;
 
-public:
+	const std::set<int> notificationThresholds = {1, 2, 3, 4, 5, 10, 20, 30};
+
+	std::map<PlayerColor, std::shared_ptr<CLabel>> playerLabelsMain;
+	std::map<PlayerColor, std::shared_ptr<CLabel>> playerLabelsBattle;
+	std::map<PlayerColor, std::shared_ptr<CLabel>> playerLabelsUnit;
+	std::shared_ptr<CFilledTexture> backgroundTexture;
+	std::shared_ptr<TransparentFilledRectangle> backgroundBorder;
+
+	void updateTimer(PlayerColor player, uint32_t msPassed);
 
 	void show(Canvas & to) override;
 	void tick(uint32_t msPassed) override;
 	
-	void setTime(PlayerColor player, int time);
+	void updateNotifications(PlayerColor player, int timeMs);
+	void updateTextLabel(PlayerColor player, const TurnTimerInfo & timer);
+
+public:
+	/// Activates adventure map mode in which widget will display timer for all players
+	TurnTimerWidget(const Point & position);
 
-	TurnTimerWidget();
+	/// Activates battle mode in which timer displays only timer of specific player
+	TurnTimerWidget(const Point & position, PlayerColor player);
 };

+ 3 - 3
client/battle/BattleActionsController.cpp

@@ -616,8 +616,8 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
 		case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
 			if(targetStack && targetStackOwned && targetStack != owner.stacksController->getActiveStack() && targetStack->alive()) //only positive spells for other allied creatures
 			{
-				int spellID = owner.getBattle()->battleGetRandomStackSpell(CRandomGenerator::getDefault(), targetStack, CBattleInfoCallback::RANDOM_GENIE);
-				return spellID > -1;
+				SpellID spellID = owner.getBattle()->getRandomBeneficialSpell(CRandomGenerator::getDefault(), owner.stacksController->getActiveStack(), targetStack);
+				return spellID != SpellID::NONE;
 			}
 			return false;
 
@@ -887,7 +887,7 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS
 	{
 		// faerie dragon can cast only one, randomly selected spell until their next move
 		//TODO: faerie dragon type spell should be selected by server
-		const auto * spellToCast = owner.getBattle()->battleGetRandomStackSpell(CRandomGenerator::getDefault(), casterStack, CBattleInfoCallback::RANDOM_AIMED).toSpell();
+		const auto * spellToCast = owner.getBattle()->getRandomCastedSpell(CRandomGenerator::getDefault(), casterStack).toSpell();
 
 		if (spellToCast)
 			creatureSpells.push_back(spellToCast);

+ 4 - 1
client/battle/BattleInterface.cpp

@@ -58,6 +58,7 @@ BattleInterface::BattleInterface(const BattleID & battleID, const CCreatureSet *
 	, curInt(att)
 	, battleID(battleID)
 	, battleOpeningDelayActive(true)
+	, round(0)
 {
 	if(spectatorInt)
 	{
@@ -107,7 +108,8 @@ void BattleInterface::playIntroSoundAndUnlockInterface()
 {
 	auto onIntroPlayed = [this]()
 	{
-		if(LOCPLINT->battleInt)
+		// Make sure that battle have not ended while intro was playing AND that a different one has not started
+		if(LOCPLINT->battleInt.get() == this)
 			onIntroSoundPlayed();
 	};
 
@@ -234,6 +236,7 @@ void BattleInterface::newRoundFirst()
 void BattleInterface::newRound()
 {
 	console->addText(CGI->generaltexth->allTexts[412]);
+	round++;
 }
 
 void BattleInterface::giveCommand(EActionType action, BattleHex tile, SpellID spell)

+ 1 - 0
client/battle/BattleInterface.h

@@ -136,6 +136,7 @@ public:
 	const CGHeroInstance *defendingHeroInstance;
 
 	bool tacticsMode;
+	ui32 round;
 
 	std::unique_ptr<BattleProjectileController> projectilesController;
 	std::unique_ptr<BattleSiegeController> siegeController;

+ 22 - 2
client/battle/BattleInterfaceClasses.cpp

@@ -34,6 +34,7 @@
 #include "../widgets/Buttons.h"
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
+#include "../widgets/MiscWidgets.h"
 #include "../windows/CMessage.h"
 #include "../windows/CSpellWindow.h"
 #include "../render/CAnimation.h"
@@ -787,11 +788,16 @@ void StackQueue::update()
 	owner.getBattle()->battleGetTurnOrder(queueData, stackBoxes.size(), 0);
 
 	size_t boxIndex = 0;
+	ui32 tmpTurn = -1;
 
 	for(size_t turn = 0; turn < queueData.size() && boxIndex < stackBoxes.size(); turn++)
 	{
 		for(size_t unitIndex = 0; unitIndex < queueData[turn].size() && boxIndex < stackBoxes.size(); boxIndex++, unitIndex++)
-			stackBoxes[boxIndex]->setUnit(queueData[turn][unitIndex], turn);
+		{
+			ui32 currentTurn = owner.round + turn;
+			stackBoxes[boxIndex]->setUnit(queueData[turn][unitIndex], turn, tmpTurn != currentTurn && owner.round != 0 && (!embedded || tmpTurn != -1) ? (std::optional<ui32>)currentTurn : std::nullopt);
+			tmpTurn = currentTurn;
+		}
 	}
 
 	for(; boxIndex < stackBoxes.size(); boxIndex++)
@@ -829,11 +835,14 @@ StackQueue::StackBox::StackBox(StackQueue * owner):
 	{
 		icon = std::make_shared<CAnimImage>(owner->icons, 0, 0, 5, 2);
 		amount = std::make_shared<CLabel>(pos.w/2, pos.h - 7, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
+		roundRect = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, 2, 48), ColorRGBA(0, 0, 0, 255), ColorRGBA(0, 255, 0, 255));
 	}
 	else
 	{
 		icon = std::make_shared<CAnimImage>(owner->icons, 0, 0, 9, 1);
 		amount = std::make_shared<CLabel>(pos.w/2, pos.h - 8, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE);
+		roundRect = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, 15, 18), ColorRGBA(0, 0, 0, 255), ColorRGBA(241, 216, 120, 255));
+		round = std::make_shared<CLabel>(4, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
 
 		int icon_x = pos.w - 17;
 		int icon_y = pos.h - 18;
@@ -841,9 +850,10 @@ StackQueue::StackBox::StackBox(StackQueue * owner):
 		stateIcon = std::make_shared<CAnimImage>(owner->stateIcons, 0, 0, icon_x, icon_y);
 		stateIcon->visible = false;
 	}
+	roundRect->disable();
 }
 
-void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
+void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn, std::optional<ui32> currentTurn)
 {
 	if(unit)
 	{
@@ -861,6 +871,16 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
 			icon->setFrame(owner->getSiegeShooterIconID(), 1);
 
 		amount->setText(TextOperations::formatMetric(unit->getCount(), 4));
+		if(currentTurn && !owner->embedded)
+		{
+			std::string tmp = std::to_string(*currentTurn);
+			int len = graphics->fonts[FONT_SMALL]->getStringWidth(tmp);
+			roundRect->pos.w = len + 6;
+			round->setText(tmp);
+		}
+		roundRect->setEnabled(currentTurn.has_value());
+		if(!owner->embedded)
+			round->setEnabled(currentTurn.has_value());
 
 		if(stateIcon)
 		{

+ 4 - 2
client/battle/BattleInterfaceClasses.h

@@ -39,6 +39,7 @@ class CToggleButton;
 class CLabel;
 class CTextBox;
 class CAnimImage;
+class TransparentFilledRectangle;
 class CPlayerInterface;
 class BattleRenderer;
 
@@ -206,6 +207,8 @@ class StackQueue : public CIntObject
 		std::shared_ptr<CAnimImage> icon;
 		std::shared_ptr<CLabel> amount;
 		std::shared_ptr<CAnimImage> stateIcon;
+		std::shared_ptr<CLabel> round;
+		std::shared_ptr<TransparentFilledRectangle> roundRect;
 
 		void show(Canvas & to) override;
 		void showAll(Canvas & to) override;
@@ -213,9 +216,8 @@ class StackQueue : public CIntObject
 		bool isBoundUnitHighlighted() const;
 	public:
 		StackBox(StackQueue * owner);
-		void setUnit(const battle::Unit * unit, size_t turn = 0);
+		void setUnit(const battle::Unit * unit, size_t turn = 0, std::optional<ui32> currentTurn = std::nullopt);
 		std::optional<uint32_t> getBoundUnitID() const;
-
 	};
 
 	static const int QUEUE_SIZE = 10;

+ 2 - 0
client/battle/BattleStacksController.cpp

@@ -691,6 +691,8 @@ void BattleStacksController::endAction(const BattleAction & action)
 
 void BattleStacksController::startAction(const BattleAction & action)
 {
+	// if timer run out and we did not act in time - deactivate current stack
+	setActiveStack(nullptr);
 	removeExpiredColorFilters();
 }
 

+ 43 - 4
client/battle/BattleWindow.cpp

@@ -31,6 +31,7 @@
 #include "../render/Canvas.h"
 #include "../render/IRenderHandler.h"
 #include "../adventureMap/CInGameConsole.h"
+#include "../adventureMap/TurnTimerWidget.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CGeneralTextHandler.h"
@@ -39,6 +40,7 @@
 #include "../../lib/CStack.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/filesystem/ResourcePath.h"
+#include "../../lib/StartInfo.h"
 #include "../windows/settings/SettingsMainWindow.h"
 
 BattleWindow::BattleWindow(BattleInterface & owner):
@@ -83,6 +85,7 @@ BattleWindow::BattleWindow(BattleInterface & owner):
 
 	createQueue();
 	createStickyHeroInfoWindows();
+	createTimerInfoWindows();
 
 	if ( owner.tacticsMode )
 		tacticPhaseStarted();
@@ -128,8 +131,8 @@ void BattleWindow::createStickyHeroInfoWindows()
 		InfoAboutHero info;
 		info.initFromHero(owner.defendingHeroInstance, InfoAboutHero::EInfoLevel::INBATTLE);
 		Point position = (GH.screenDimensions().x >= 1000)
-				? Point(pos.x + pos.w + 15, pos.y)
-				: Point(pos.x + pos.w -79, pos.y + 135);
+				? Point(pos.x + pos.w + 15, pos.y + 60)
+				: Point(pos.x + pos.w -79, pos.y + 195);
 		defenderHeroWindow = std::make_shared<HeroInfoBasicPanel>(info, &position);
 	}
 	if(owner.attackingHeroInstance)
@@ -137,8 +140,8 @@ void BattleWindow::createStickyHeroInfoWindows()
 		InfoAboutHero info;
 		info.initFromHero(owner.attackingHeroInstance, InfoAboutHero::EInfoLevel::INBATTLE);
 		Point position = (GH.screenDimensions().x >= 1000)
-				? Point(pos.x - 93, pos.y)
-				: Point(pos.x + 1, pos.y + 135);
+				? Point(pos.x - 93, pos.y + 60)
+				: Point(pos.x + 1, pos.y + 195);
 		attackerHeroWindow = std::make_shared<HeroInfoBasicPanel>(info, &position);
 	}
 
@@ -154,6 +157,33 @@ void BattleWindow::createStickyHeroInfoWindows()
 	}
 }
 
+void BattleWindow::createTimerInfoWindows()
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.battleTimer != 0 || LOCPLINT->cb->getStartInfo()->turnTimerInfo.unitTimer != 0)
+	{
+		PlayerColor attacker = owner.getBattle()->sideToPlayer(BattleSide::ATTACKER);
+		PlayerColor defender = owner.getBattle()->sideToPlayer(BattleSide::DEFENDER);
+
+		if (attacker.isValidPlayer())
+		{
+			if (GH.screenDimensions().x >= 1000)
+				attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(-92, 1), attacker);
+			else
+				attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(1, 135), attacker);
+		}
+
+		if (defender.isValidPlayer())
+		{
+			if (GH.screenDimensions().x >= 1000)
+				defenderTimerWidget = std::make_shared<TurnTimerWidget>(Point(pos.w + 16, 1), defender);
+			else
+				defenderTimerWidget = std::make_shared<TurnTimerWidget>(Point(pos.w - 78, 135), defender);
+		}
+	}
+}
+
 BattleWindow::~BattleWindow()
 {
 	CPlayerInterface::battleInt = nullptr;
@@ -557,6 +587,15 @@ void BattleWindow::bSpellf()
 			LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683])
 										% heroName % CGI->artifacts()->getByIndex(artID)->getNameTranslated()));
 		}
+		else if(blockingBonus->source == BonusSource::OBJECT_TYPE)
+		{
+			if(blockingBonus->sid.as<MapObjectID>() == Obj::GARRISON || blockingBonus->sid.as<MapObjectID>() == Obj::GARRISON2)
+				LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[684]);
+		}
+	}
+	else
+	{
+		logGlobal->warn("Unexpected problem with readiness to cast spell");
 	}
 }
 

+ 5 - 0
client/battle/BattleWindow.h

@@ -24,6 +24,7 @@ class BattleInterface;
 class BattleConsole;
 class BattleRenderer;
 class StackQueue;
+class TurnTimerWidget;
 class HeroInfoBasicPanel;
 
 /// GUI object that handles functionality of panel at the bottom of combat screen
@@ -36,6 +37,9 @@ class BattleWindow : public InterfaceObjectConfigurable
 	std::shared_ptr<HeroInfoBasicPanel> attackerHeroWindow;
 	std::shared_ptr<HeroInfoBasicPanel> defenderHeroWindow;
 
+	std::shared_ptr<TurnTimerWidget> attackerTimerWidget;
+	std::shared_ptr<TurnTimerWidget> defenderTimerWidget;
+
 	/// button press handling functions
 	void bOptionsf();
 	void bSurrenderf();
@@ -65,6 +69,7 @@ class BattleWindow : public InterfaceObjectConfigurable
 
 	void toggleStickyHeroWindowsVisibility();
 	void createStickyHeroInfoWindows();
+	void createTimerInfoWindows();
 
 	std::shared_ptr<BattleConsole> buildBattleConsole(const JsonNode &) const;
 

+ 2 - 4
client/lobby/CBonusSelection.cpp

@@ -273,17 +273,15 @@ void CBonusSelection::createBonusesIcons()
 		}
 
 		case CampaignBonusType::HERO:
-
-			desc.appendLocalString(EMetaText::GENERAL_TXT, 718);
-			desc.replaceTextID(TextIdentifier("core", "genrltxt", "capColors", bonDescs[i].info1).get());
 			if(bonDescs[i].info2 == 0xFFFF)
 			{
-				desc.replaceLocalString(EMetaText::GENERAL_TXT, 101);
+				desc.appendLocalString(EMetaText::GENERAL_TXT, 720); // Start with random hero
 				picNumber = -1;
 				picName = "CBONN1A3.BMP";
 			}
 			else
 			{
+				desc.appendLocalString(EMetaText::GENERAL_TXT, 715); // Start with %s
 				desc.replaceTextID(CGI->heroh->objects[bonDescs[i].info2]->getNameTextID());
 			}
 			break;

+ 2 - 0
client/lobby/CLobbyScreen.cpp

@@ -120,6 +120,8 @@ void CLobbyScreen::toggleTab(std::shared_ptr<CIntObject> tab)
 		CSH->sendGuiAction(LobbyGuiAction::OPEN_SCENARIO_LIST);
 	else if(tab == tabRand)
 		CSH->sendGuiAction(LobbyGuiAction::OPEN_RANDOM_MAP_OPTIONS);
+	else if(tab == tabTurnOptions)
+		CSH->sendGuiAction(LobbyGuiAction::OPEN_TURN_OPTIONS);
 	CSelectionBase::toggleTab(tab);
 }
 

+ 6 - 3
client/lobby/CSelectionBase.cpp

@@ -135,7 +135,7 @@ InfoCard::InfoCard()
 	Rect descriptionRect(26, 149, 320, 115);
 	mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
 	playerListBg = std::make_shared<CPicture>(ImagePath::builtin("CHATPLUG.bmp"), 16, 276);
-	chat = std::make_shared<CChatBox>(Rect(26, 132, 340, 132));
+	chat = std::make_shared<CChatBox>(Rect(18, 126, 335, 143));
 
 	if(SEL->screenType == ESelectionScreen::campaignList)
 	{
@@ -332,9 +332,12 @@ CChatBox::CChatBox(const Rect & rect)
 	setRedrawParent(true);
 
 	const int height = static_cast<int>(graphics->fonts[FONT_SMALL]->getLineHeight());
-	inputBox = std::make_shared<CTextInput>(Rect(0, rect.h - height, rect.w, height), EFonts::FONT_SMALL, 0);
+	Rect textInputArea(1, rect.h - height, rect.w - 1, height);
+	Rect chatHistoryArea(3, 1, rect.w - 3, rect.h - height - 1);
+	inputBackground = std::make_shared<TransparentFilledRectangle>(textInputArea, ColorRGBA(0,0,0,192));
+	inputBox = std::make_shared<CTextInput>(textInputArea, EFonts::FONT_SMALL, 0);
 	inputBox->removeUsedEvents(KEYBOARD);
-	chatHistory = std::make_shared<CTextBox>("", Rect(0, 0, rect.w, rect.h - height), 1);
+	chatHistory = std::make_shared<CTextBox>("", chatHistoryArea, 1);
 
 	chatHistory->label->color = Colors::GREEN;
 }

+ 2 - 0
client/lobby/CSelectionBase.h

@@ -33,6 +33,7 @@ class CChatBox;
 class CLabel;
 class CFlagBox;
 class CLabelGroup;
+class TransparentFilledRectangle;
 
 class ISelectionScreenInfo
 {
@@ -122,6 +123,7 @@ class CChatBox : public CIntObject
 public:
 	std::shared_ptr<CTextBox> chatHistory;
 	std::shared_ptr<CTextInput> inputBox;
+	std::shared_ptr<TransparentFilledRectangle> inputBackground;
 
 	CChatBox(const Rect & rect);
 

+ 1 - 1
client/lobby/OptionsTab.cpp

@@ -503,7 +503,7 @@ void OptionsTab::SelectionWindow::recreate()
 			int count = 0;
 			for(auto & elem : allowedHeroes)
 			{
-				CHero * type = VLC->heroh->objects[elem];
+				const CHero * type = elem.toHeroType();
 				if(type->heroClass->faction == selectedFaction)
 				{
 					count++;

+ 5 - 1
client/lobby/RandomMapTab.cpp

@@ -358,7 +358,11 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 	}
 	for(auto r : VLC->roadTypeHandler->objects)
 	{
-		if(auto w = widget<CToggleButton>(r->getJsonKey()))
+		// Workaround for vcmi-extras bug
+		std::string jsonKey = r->getJsonKey();
+		std::string identifier = jsonKey.substr(jsonKey.find(':')+1);
+
+		if(auto w = widget<CToggleButton>(identifier))
 		{
 			w->setSelected(opts->isRoadEnabled(r->getId()));
 		}

+ 2 - 3
client/mainmenu/CHighScoreScreen.cpp

@@ -29,8 +29,7 @@
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/constants/EntityIdentifiers.h"
 #include "../../lib/TextOperations.h"
-
-#include "vstd/DateUtils.h"
+#include "../../lib/Languages.h"
 
 auto HighScoreCalculation::calculate()
 {
@@ -264,7 +263,7 @@ int CHighScoreInputScreen::addEntry(std::string text) {
 		newNode["scenarioName"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].scenarioName;
 	newNode["days"].Integer() = calc.calculate().sumDays;
 	newNode["points"].Integer() = calc.calculate().cheater ? 0 : calc.calculate().total;
-	newNode["datetime"].String() = vstd::getFormattedDateTime(std::time(nullptr));
+	newNode["datetime"].String() = TextOperations::getFormattedDateTimeLocal(std::time(nullptr));
 	newNode["posFlag"].Bool() = true;
 
 	baseNode.push_back(newNode);

+ 20 - 3
client/mainmenu/CMainMenu.cpp

@@ -382,12 +382,29 @@ void CMainMenu::openCampaignLobby(std::shared_ptr<CampaignState> campaign)
 
 void CMainMenu::openCampaignScreen(std::string name)
 {
-	if(vstd::contains(CMainMenuConfig::get().getCampaigns().Struct(), name))
+	auto const & config = CMainMenuConfig::get().getCampaigns();
+
+	if(!vstd::contains(config.Struct(), name))
 	{
-		GH.windows().createAndPushWindow<CCampaignScreen>(CMainMenuConfig::get().getCampaigns(), name);
+		logGlobal->error("Unknown campaign set: %s", name);
 		return;
 	}
-	logGlobal->error("Unknown campaign set: %s", name);
+
+	bool campaignsFound = true;
+	for (auto const & entry : config[name]["items"].Vector())
+	{
+		ResourcePath resourceID(entry["file"].String(), EResType::CAMPAIGN);
+		if (!CResourceHandler::get()->existsResource(resourceID))
+			campaignsFound = false;
+	}
+
+	if (!campaignsFound)
+	{
+		CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.client.errors.missingCampaigns"), std::vector<std::shared_ptr<CComponent>>(), PlayerColor(1));
+		return;
+	}
+
+	GH.windows().createAndPushWindow<CCampaignScreen>(config, name);
 }
 
 void CMainMenu::startTutorial()

+ 3 - 0
client/mapView/MapViewController.cpp

@@ -269,6 +269,9 @@ void MapViewController::afterRender()
 
 bool MapViewController::isEventInstant(const CGObjectInstance * obj, const PlayerColor & initiator)
 {
+	if(settings["gameTweaks"]["skipAdventureMapAnimations"].Bool())
+		return true;
+
 	if (!isEventVisible(obj, initiator))
 		return true;
 

+ 30 - 6
client/widgets/CArtifactHolder.cpp

@@ -139,7 +139,7 @@ void CCommanderArtPlace::clickPressed(const Point & cursorPosition)
 		LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.commanderWindow.artifactMessage"), [this]() { returnArtToHeroCallback(); }, []() {});
 }
 
-void CCommanderArtPlace::showPopupWindow(const Point & cursorPosition)
+void CCommanderArtPlace::showPopupWindow(const Point& cursorPosition)
 {
 	if(ourArt && text.size())
 		CArtPlace::showPopupWindow(cursorPosition);
@@ -180,16 +180,25 @@ bool CArtPlace::isSelected() const
 	return selection->visible;
 }
 
-void CHeroArtPlace::clickPressed(const Point & cursorPosition)
+void CArtPlace::clickPressed(const Point & cursorPosition)
 {
-	if(leftClickCallback)
-		leftClickCallback(*this);
+	if(clickPressedCallback)
+		clickPressedCallback(*this, cursorPosition);
 }
 
-void CHeroArtPlace::showPopupWindow(const Point & cursorPosition)
+void CArtPlace::showPopupWindow(const Point & cursorPosition)
 {
 	if(showPopupCallback)
-		showPopupCallback(*this);
+		showPopupCallback(*this, cursorPosition);
+}
+
+void CArtPlace::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
+{
+	if(!on)
+		return;
+
+	if(gestureCallback)
+		gestureCallback(*this, initialPosition);
 }
 
 void CArtPlace::showAll(Canvas & to)
@@ -216,6 +225,21 @@ void CArtPlace::setArtifact(const CArtifactInstance * art)
 	}
 }
 
+void CArtPlace::setClickPressedCallback(ClickFunctor callback)
+{
+	clickPressedCallback = callback;
+}
+
+void CArtPlace::setShowPopupCallback(ClickFunctor callback)
+{
+	showPopupCallback = callback;
+}
+
+void CArtPlace::setGestureCallback(ClickFunctor callback)
+{
+	gestureCallback = callback;
+}
+
 void CHeroArtPlace::addCombinedArtInfo(std::map<const CArtifact*, int> & arts)
 {
 	for(const auto & combinedArt : arts)

+ 14 - 9
client/widgets/CArtifactHolder.h

@@ -32,14 +32,24 @@ public:
 class CArtPlace : public LRClickableAreaWTextComp
 {
 public:
+	using ClickFunctor = std::function<void(CArtPlace&, const Point&)>;
+
+	ArtifactPosition slot;
+
 	CArtPlace(Point position, const CArtifactInstance * art = nullptr);
-	const CArtifactInstance* getArt();
+	const CArtifactInstance * getArt();
 	void lockSlot(bool on);
 	bool isLocked() const;
 	void selectSlot(bool on);
 	bool isSelected() const;
 	void showAll(Canvas & to) override;
 	void setArtifact(const CArtifactInstance * art);
+	void setClickPressedCallback(ClickFunctor callback);
+	void setShowPopupCallback(ClickFunctor callback);
+	void setGestureCallback(ClickFunctor callback);
+	void clickPressed(const Point & cursorPosition) override;
+	void showPopupWindow(const Point & cursorPosition) override;
+	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
 
 protected:
 	std::shared_ptr<CAnimImage> image;
@@ -47,6 +57,9 @@ protected:
 	int imageIndex;
 	std::shared_ptr<CAnimImage> selection;
 	bool locked;
+	ClickFunctor clickPressedCallback;
+	ClickFunctor showPopupCallback;
+	ClickFunctor gestureCallback;
 
 	void setInternals(const CArtifactInstance * artInst);
 };
@@ -68,15 +81,7 @@ public:
 class CHeroArtPlace: public CArtPlace
 {
 public:
-	using ClickFunctor = std::function<void(CHeroArtPlace&)>;
-
-	ArtifactPosition slot;
-	ClickFunctor leftClickCallback;
-	ClickFunctor showPopupCallback;
-
 	CHeroArtPlace(Point position, const CArtifactInstance * art = nullptr);
-	void clickPressed(const Point & cursorPosition) override;
-	void showPopupWindow(const Point & cursorPosition) override;
 	void addCombinedArtInfo(std::map<const CArtifact*, int> & arts);
 };
 

+ 3 - 3
client/widgets/CArtifactsOfHeroAltar.cpp

@@ -23,8 +23,8 @@ CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position)
 	: visibleArtSet(ArtBearer::ArtBearer::HERO)
 {
 	init(
-		std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1), 
-		std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1), 
+		std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2),
+		std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2),
 		position,
 		std::bind(&CArtifactsOfHeroAltar::scrollBackpack, this, _1));
 	pickedArtFromSlot = ArtifactPosition::PRE_FIRST;
@@ -69,7 +69,7 @@ void CArtifactsOfHeroAltar::scrollBackpack(int offset)
 	redraw();
 }
 
-void CArtifactsOfHeroAltar::pickUpArtifact(CHeroArtPlace & artPlace)
+void CArtifactsOfHeroAltar::pickUpArtifact(CArtPlace & artPlace)
 {
 	if(const auto art = artPlace.getArt())
 	{

+ 1 - 1
client/widgets/CArtifactsOfHeroAltar.h

@@ -26,7 +26,7 @@ public:
 	void updateWornSlots() override;
 	void updateBackpackSlots() override;
 	void scrollBackpack(int offset) override;
-	void pickUpArtifact(CHeroArtPlace & artPlace);
+	void pickUpArtifact(CArtPlace & artPlace);
 	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
 	void pickedArtMoveToAltar(const ArtifactPosition & slot);
 	void deleteFromVisible(const CArtifactInstance * artInst);

+ 148 - 40
client/widgets/CArtifactsOfHeroBackpack.cpp

@@ -17,37 +17,91 @@
 #include "ObjectLists.h"
 
 #include "../CPlayerInterface.h"
+#include "../../lib/ArtifactUtils.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/networkPacks/ArtifactLocation.h"
 
 #include "../../CCallback.h"
 
-CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(const Point & position)
+CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(size_t slotsColumnsMax, size_t slotsRowsMax)
+	: slotsColumnsMax(slotsColumnsMax)
+	, slotsRowsMax(slotsRowsMax)
 {
-	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
-	pos += position;
 	setRedrawParent(true);
+}
 
+CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack()
+	: CArtifactsOfHeroBackpack(8, 8)
+{
 	const auto backpackCap = VLC->settings()->getInteger(EGameSettings::HEROES_BACKPACK_CAP);
-	auto visibleCapacityMax = HERO_BACKPACK_WINDOW_SLOT_ROWS * HERO_BACKPACK_WINDOW_SLOT_COLUMNS;
+	auto visibleCapacityMax = slotsRowsMax * slotsColumnsMax;
 	if(backpackCap >= 0)
 		visibleCapacityMax = visibleCapacityMax > backpackCap ? backpackCap : visibleCapacityMax;
 
-	backpack.resize(visibleCapacityMax);
+	initAOHbackpack(visibleCapacityMax, backpackCap < 0 || visibleCapacityMax < backpackCap);
+}
+
+void CArtifactsOfHeroBackpack::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc)
+{
+	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
+}
+
+void CArtifactsOfHeroBackpack::pickUpArtifact(CArtPlace & artPlace)
+{
+	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
+		ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));
+}
+
+void CArtifactsOfHeroBackpack::scrollBackpack(int offset)
+{
+	if(backpackListBox)
+		backpackListBox->resize(getActiveSlotRowsNum());
+	backpackPos += offset;
+	auto slot = ArtifactPosition::BACKPACK_START + backpackPos;
+	for(auto artPlace : backpack)
+	{
+		setSlotData(artPlace, slot, *curHero);
+		slot = slot + 1;
+	}
+	redraw();
+}
+
+void CArtifactsOfHeroBackpack::updateBackpackSlots()
+{
+	if(backpackListBox)
+		backpackListBox->resize(getActiveSlotRowsNum());
+	CArtifactsOfHeroBase::updateBackpackSlots();
+}
+
+size_t CArtifactsOfHeroBackpack::getActiveSlotRowsNum()
+{
+	return (curHero->artifactsInBackpack.size() + slotsColumnsMax - 1) / slotsColumnsMax;
+}
+
+size_t CArtifactsOfHeroBackpack::getSlotsNum()
+{
+	return backpack.size();
+}
+
+void CArtifactsOfHeroBackpack::initAOHbackpack(size_t slots, bool slider)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
+
+	backpack.resize(slots);
 	size_t artPlaceIdx = 0;
 	for(auto & artPlace : backpack)
 	{
-		const auto pos = Point(slotSizeWithMargin * (artPlaceIdx % HERO_BACKPACK_WINDOW_SLOT_COLUMNS),
-			slotSizeWithMargin * (artPlaceIdx / HERO_BACKPACK_WINDOW_SLOT_COLUMNS));
+		const auto pos = Point(slotSizeWithMargin * (artPlaceIdx % slotsColumnsMax),
+			slotSizeWithMargin * (artPlaceIdx / slotsColumnsMax));
 		backpackSlotsBackgrounds.emplace_back(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/artifactSlotEmpty"), pos));
 		artPlace = std::make_shared<CHeroArtPlace>(pos);
 		artPlace->setArtifact(nullptr);
-		artPlace->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
-		artPlace->showPopupCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
+		artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
+		artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
 		artPlaceIdx++;
 	}
 
-	if(backpackCap < 0 || visibleCapacityMax < backpackCap)
+	if(slider)
 	{
 		auto onCreate = [](size_t index) -> std::shared_ptr<CIntObject>
 		{
@@ -55,57 +109,111 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(const Point & position)
 		};
 		CListBoxWithCallback::MovedPosCallback posMoved = [this](size_t pos) -> void
 		{
-			scrollBackpack(static_cast<int>(pos) * HERO_BACKPACK_WINDOW_SLOT_COLUMNS - backpackPos);
+			scrollBackpack(static_cast<int>(pos) * slotsColumnsMax - backpackPos);
 		};
 		backpackListBox = std::make_shared<CListBoxWithCallback>(
-				posMoved, onCreate, Point(0, 0), Point(0, 0), HERO_BACKPACK_WINDOW_SLOT_ROWS, 0, 0, 1,
-				Rect(HERO_BACKPACK_WINDOW_SLOT_COLUMNS * slotSizeWithMargin + sliderPosOffsetX, 0, HERO_BACKPACK_WINDOW_SLOT_ROWS * slotSizeWithMargin - 2, 0));
+			posMoved, onCreate, Point(0, 0), Point(0, 0), slotsRowsMax, 0, 0, 1,
+			Rect(slotsColumnsMax * slotSizeWithMargin + sliderPosOffsetX, 0, slotsRowsMax * slotSizeWithMargin - 2, 0));
 	}
 
-	pos.w = visibleCapacityMax > HERO_BACKPACK_WINDOW_SLOT_COLUMNS ? HERO_BACKPACK_WINDOW_SLOT_COLUMNS : visibleCapacityMax;
+	pos.w = slots > slotsColumnsMax ? slotsColumnsMax : slots;
 	pos.w *= slotSizeWithMargin;
-	if(backpackListBox)
+	if(slider)
 		pos.w += sliderPosOffsetX + 16; // 16 is slider width. TODO: get it from CListBox directly;
-
-	pos.h = (visibleCapacityMax / HERO_BACKPACK_WINDOW_SLOT_COLUMNS);
-	if(visibleCapacityMax % HERO_BACKPACK_WINDOW_SLOT_COLUMNS != 0)
-		pos.h += 1;
-	pos.h *= slotSizeWithMargin;
+	pos.h = calcRows(slots) * slotSizeWithMargin;
 }
 
-void CArtifactsOfHeroBackpack::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc)
+size_t CArtifactsOfHeroBackpack::calcRows(size_t slots)
 {
-	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
+	size_t rows = 0;
+	if(slotsColumnsMax != 0)
+	{
+		rows = slots / slotsColumnsMax;
+		if(slots % slotsColumnsMax != 0)
+			rows += 1;
+	}
+	return rows;
 }
 
-void CArtifactsOfHeroBackpack::pickUpArtifact(CHeroArtPlace & artPlace)
+CArtifactsOfHeroQuickBackpack::CArtifactsOfHeroQuickBackpack(const ArtifactPosition filterBySlot)
+	: CArtifactsOfHeroBackpack(0, 0)
 {
-	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
-		ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));
+	assert(filterBySlot != ArtifactPosition::FIRST_AVAILABLE);
+
+	if(!ArtifactUtils::isSlotEquipment(filterBySlot))
+		return;
+
+	this->filterBySlot = filterBySlot;
 }
 
-void CArtifactsOfHeroBackpack::scrollBackpack(int offset)
+void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero)
 {
-	if(backpackListBox)
-		backpackListBox->resize(getActiveSlotRowsNum());
-	backpackPos += offset;
-	auto slot = ArtifactPosition::BACKPACK_START + backpackPos;
-	for(auto artPlace : backpack)
+	if(curHero == hero)
+		return;
+	
+	curHero = hero;
+	if(curHero)
 	{
-		setSlotData(artPlace, slot, *curHero);
-		slot = slot + 1;
+		ArtifactID artInSlotId = ArtifactID::NONE;
+		SpellID scrollInSlotSpellId = SpellID::NONE;
+		if(auto artInSlot = curHero->getArt(filterBySlot))
+		{
+			artInSlotId = artInSlot->getTypeId();
+			scrollInSlotSpellId = artInSlot->getScrollSpellID();
+		}
+
+		std::map<const ArtifactID, const CArtifactInstance*> filteredArts;
+		for(auto & slotInfo : curHero->artifactsInBackpack)
+			if(slotInfo.artifact->getTypeId() != artInSlotId &&	!slotInfo.artifact->isScroll() &&
+				slotInfo.artifact->artType->canBePutAt(curHero, filterBySlot, true))
+			{
+				filteredArts.insert(std::pair(slotInfo.artifact->getTypeId(), slotInfo.artifact));
+			}
+
+		std::map<const SpellID, const CArtifactInstance*> filteredScrolls;
+		if(filterBySlot == ArtifactPosition::MISC1 || filterBySlot == ArtifactPosition::MISC2 || filterBySlot == ArtifactPosition::MISC3 ||
+			filterBySlot == ArtifactPosition::MISC4 || filterBySlot == ArtifactPosition::MISC5)
+		{
+			for(auto & slotInfo : curHero->artifactsInBackpack)
+			{
+				if(slotInfo.artifact->isScroll() && slotInfo.artifact->getScrollSpellID() != scrollInSlotSpellId)
+					filteredScrolls.insert(std::pair(slotInfo.artifact->getScrollSpellID(), slotInfo.artifact));
+			}
+		}
+
+		backpack.clear();
+		auto requiredSlots = filteredArts.size() + filteredScrolls.size();
+		slotsColumnsMax = ceilf(sqrtf(requiredSlots));
+		slotsRowsMax = calcRows(requiredSlots);
+		initAOHbackpack(requiredSlots, false);
+		auto artPlace = backpack.begin();
+		for(auto & art : filteredArts)
+			setSlotData(*artPlace++, curHero->getSlotByInstance(art.second), *curHero);
+		for(auto & art : filteredScrolls)
+			setSlotData(*artPlace++, curHero->getSlotByInstance(art.second), *curHero);
 	}
-	redraw();
 }
 
-void CArtifactsOfHeroBackpack::updateBackpackSlots()
+ArtifactPosition CArtifactsOfHeroQuickBackpack::getFilterSlot()
 {
-	if(backpackListBox)
-		backpackListBox->resize(getActiveSlotRowsNum());
-	CArtifactsOfHeroBase::updateBackpackSlots();
+	return filterBySlot;
 }
 
-size_t CArtifactsOfHeroBackpack::getActiveSlotRowsNum()
+void CArtifactsOfHeroQuickBackpack::selectSlotAt(const Point & position)
 {
-	return (curHero->artifactsInBackpack.size() + HERO_BACKPACK_WINDOW_SLOT_COLUMNS - 1) / HERO_BACKPACK_WINDOW_SLOT_COLUMNS;
+	for(auto & artPlace : backpack)
+		artPlace->selectSlot(artPlace->pos.isInside(position));
 }
+
+void CArtifactsOfHeroQuickBackpack::swapSelected()
+{
+	ArtifactLocation backpackLoc(curHero->id, ArtifactPosition::PRE_FIRST);
+	for(auto & artPlace : backpack)
+		if(artPlace->isSelected())
+		{
+			backpackLoc.slot = artPlace->slot;
+			break;
+		}
+	if(backpackLoc.slot != ArtifactPosition::PRE_FIRST && filterBySlot != ArtifactPosition::PRE_FIRST && curHero)
+		swapArtifacts(backpackLoc, ArtifactLocation(curHero->id, filterBySlot));
+}

+ 24 - 6
client/widgets/CArtifactsOfHeroBackpack.h

@@ -22,18 +22,36 @@ class CListBoxWithCallback;
 class CArtifactsOfHeroBackpack : public CArtifactsOfHeroBase
 {
 public:
-	CArtifactsOfHeroBackpack(const Point & position);
+	CArtifactsOfHeroBackpack(size_t slotsColumnsMax, size_t slotsRowsMax);
+	CArtifactsOfHeroBackpack();
 	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
-	void pickUpArtifact(CHeroArtPlace & artPlace);
+	void pickUpArtifact(CArtPlace & artPlace);
 	void scrollBackpack(int offset) override;
 	void updateBackpackSlots() override;
 	size_t getActiveSlotRowsNum();
+	size_t getSlotsNum();
 
-private:
+protected:
 	std::shared_ptr<CListBoxWithCallback> backpackListBox;
 	std::vector<std::shared_ptr<CPicture>> backpackSlotsBackgrounds;
-	const size_t HERO_BACKPACK_WINDOW_SLOT_COLUMNS = 8;
-	const size_t HERO_BACKPACK_WINDOW_SLOT_ROWS = 8;
+	size_t slotsColumnsMax;
+	size_t slotsRowsMax;
 	const int slotSizeWithMargin = 46;
-	const int sliderPosOffsetX = 10;
+	const int sliderPosOffsetX = 5;
+
+	void initAOHbackpack(size_t slots, bool slider);
+	size_t calcRows(size_t slots);
+};
+
+class CArtifactsOfHeroQuickBackpack : public CArtifactsOfHeroBackpack
+{
+public:
+	CArtifactsOfHeroQuickBackpack(const ArtifactPosition filterBySlot);
+	void setHero(const CGHeroInstance * hero);
+	ArtifactPosition getFilterSlot();
+	void selectSlotAt(const Point & position);
+	void swapSelected();
+
+private:
+	ArtifactPosition filterBySlot;
 };

+ 26 - 11
client/widgets/CArtifactsOfHeroBase.cpp

@@ -56,8 +56,8 @@ void CArtifactsOfHeroBase::setPutBackPickedArtifactCallback(PutBackPickedArtCall
 }
 
 void CArtifactsOfHeroBase::init(
-	CHeroArtPlace::ClickFunctor lClickCallback,
-	CHeroArtPlace::ClickFunctor showPopupCallback,
+	CArtPlace::ClickFunctor lClickCallback,
+	CArtPlace::ClickFunctor showPopupCallback,
 	const Point & position,
 	BpackScrollFunctor scrollCallback)
 {
@@ -78,14 +78,14 @@ void CArtifactsOfHeroBase::init(
 	{
 		artPlace.second->slot = artPlace.first;
 		artPlace.second->setArtifact(nullptr);
-		artPlace.second->leftClickCallback = lClickCallback;
-		artPlace.second->showPopupCallback = showPopupCallback;
+		artPlace.second->setClickPressedCallback(lClickCallback);
+		artPlace.second->setShowPopupCallback(showPopupCallback);
 	}
 	for(auto artPlace : backpack)
 	{
 		artPlace->setArtifact(nullptr);
-		artPlace->leftClickCallback = lClickCallback;
-		artPlace->showPopupCallback = showPopupCallback;
+		artPlace->setClickPressedCallback(lClickCallback);
+		artPlace->setShowPopupCallback(showPopupCallback);
 	}
 	leftBackpackRoll = std::make_shared<CButton>(Point(379, 364), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(), [scrollCallback]() {scrollCallback(-1);}, EShortcut::MOVE_LEFT);
 	rightBackpackRoll = std::make_shared<CButton>(Point(632, 364), AnimationPath::builtin("hsbtns5.def"), CButton::tooltip(), [scrollCallback]() {scrollCallback(+1);}, EShortcut::MOVE_RIGHT);
@@ -95,16 +95,22 @@ void CArtifactsOfHeroBase::init(
 	setRedrawParent(true);
 }
 
-void CArtifactsOfHeroBase::leftClickArtPlace(CHeroArtPlace & artPlace)
+void CArtifactsOfHeroBase::clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
 {
-	if(leftClickCallback)
-		leftClickCallback(*this, artPlace);
+	if(clickPressedCallback)
+		clickPressedCallback(*this, artPlace, cursorPosition);
 }
 
-void CArtifactsOfHeroBase::rightClickArtPlace(CHeroArtPlace & artPlace)
+void CArtifactsOfHeroBase::showPopupArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
 {
 	if(showPopupCallback)
-		showPopupCallback(*this, artPlace);
+		showPopupCallback(*this, artPlace, cursorPosition);
+}
+
+void CArtifactsOfHeroBase::gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
+{
+	if(gestureCallback)
+		gestureCallback(*this, artPlace, cursorPosition);
 }
 
 void CArtifactsOfHeroBase::setHero(const CGHeroInstance * hero)
@@ -241,6 +247,15 @@ const CArtifactInstance * CArtifactsOfHeroBase::getPickedArtifact()
 		return curHero->getArt(ArtifactPosition::TRANSITION_POS);
 }
 
+void CArtifactsOfHeroBase::addGestureCallback(CArtPlace::ClickFunctor callback)
+{
+	for(auto & artPlace : artWorn)
+	{
+		artPlace.second->setGestureCallback(callback);
+		artPlace.second->addUsedEvents(GESTURE);
+	}
+}
+
 void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosition & slot, const CArtifactSet & artSet)
 {
 	// Spurious call from artifactMoved in attempt to update hidden backpack slot

+ 7 - 4
client/widgets/CArtifactsOfHeroBase.h

@@ -21,17 +21,19 @@ protected:
 
 public:
 	using ArtPlaceMap = std::map<ArtifactPosition, ArtPlacePtr>;
-	using ClickFunctor = std::function<void(CArtifactsOfHeroBase&, CHeroArtPlace&)>;
+	using ClickFunctor = std::function<void(CArtifactsOfHeroBase&, CArtPlace&, const Point&)>;
 	using PutBackPickedArtCallback = std::function<void()>;
 
-	ClickFunctor leftClickCallback;
+	ClickFunctor clickPressedCallback;
 	ClickFunctor showPopupCallback;
+	ClickFunctor gestureCallback;
 	
 	CArtifactsOfHeroBase();
 	virtual void putBackPickedArtifact();
 	virtual void setPutBackPickedArtifactCallback(PutBackPickedArtCallback callback);
-	virtual void leftClickArtPlace(CHeroArtPlace & artPlace);
-	virtual void rightClickArtPlace(CHeroArtPlace & artPlace);
+	virtual void clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
+	virtual void showPopupArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
+	virtual void gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
 	virtual void setHero(const CGHeroInstance * hero);
 	virtual const CGHeroInstance * getHero() const;
 	virtual void scrollBackpack(int offset);
@@ -42,6 +44,7 @@ public:
 	virtual void updateBackpackSlots();
 	virtual void updateSlot(const ArtifactPosition & slot);
 	virtual const CArtifactInstance * getPickedArtifact();
+	void addGestureCallback(CArtPlace::ClickFunctor callback);
 
 protected:
 	const CGHeroInstance * curHero;

+ 6 - 5
client/widgets/CArtifactsOfHeroKingdom.cpp

@@ -30,14 +30,15 @@ CArtifactsOfHeroKingdom::CArtifactsOfHeroKingdom(ArtPlaceMap ArtWorn, std::vecto
 	{
 		artPlace.second->slot = artPlace.first;
 		artPlace.second->setArtifact(nullptr);
-		artPlace.second->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
-		artPlace.second->showPopupCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
+		artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
+		artPlace.second->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
 	}
+	addGestureCallback(std::bind(&CArtifactsOfHeroBase::gestureArtPlace, this, _1, _2));
 	for(auto artPlace : backpack)
 	{
 		artPlace->setArtifact(nullptr);
-		artPlace->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
-		artPlace->showPopupCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
+		artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
+		artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
 	}
 	leftBackpackRoll->addCallback(std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, -1));
 	rightBackpackRoll->addCallback(std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, +1));
@@ -55,7 +56,7 @@ void CArtifactsOfHeroKingdom::swapArtifacts(const ArtifactLocation & srcLoc, con
 	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
 }
 
-void CArtifactsOfHeroKingdom::pickUpArtifact(CHeroArtPlace & artPlace)
+void CArtifactsOfHeroKingdom::pickUpArtifact(CArtPlace & artPlace)
 {
 	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
 		ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));

+ 1 - 1
client/widgets/CArtifactsOfHeroKingdom.h

@@ -24,5 +24,5 @@ public:
 		std::shared_ptr<CButton> leftScroll, std::shared_ptr<CButton> rightScroll);
 	~CArtifactsOfHeroKingdom();
 	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
-	void pickUpArtifact(CHeroArtPlace & artPlace);
+	void pickUpArtifact(CArtPlace & artPlace);
 };

+ 4 - 3
client/widgets/CArtifactsOfHeroMain.cpp

@@ -19,10 +19,11 @@
 CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position)
 {
 	init(
-		std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1),
-		std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1),
+		std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2),
+		std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2),
 		position,
 		std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1));
+	addGestureCallback(std::bind(&CArtifactsOfHeroBase::gestureArtPlace, this, _1, _2));
 }
 
 CArtifactsOfHeroMain::~CArtifactsOfHeroMain()
@@ -35,7 +36,7 @@ void CArtifactsOfHeroMain::swapArtifacts(const ArtifactLocation & srcLoc, const
 	LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
 }
 
-void CArtifactsOfHeroMain::pickUpArtifact(CHeroArtPlace & artPlace)
+void CArtifactsOfHeroMain::pickUpArtifact(CArtPlace & artPlace)
 {
 	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
 		ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));

+ 1 - 1
client/widgets/CArtifactsOfHeroMain.h

@@ -23,5 +23,5 @@ public:
 	CArtifactsOfHeroMain(const Point & position);
 	~CArtifactsOfHeroMain();
 	void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
-	void pickUpArtifact(CHeroArtPlace & artPlace);
+	void pickUpArtifact(CArtPlace & artPlace);
 };

+ 2 - 2
client/widgets/CArtifactsOfHeroMarket.cpp

@@ -15,8 +15,8 @@
 CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position)
 {
 	init(
-		std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1), 
-		std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1), 
+		std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2),
+		std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2),
 		position,
 		std::bind(&CArtifactsOfHeroMarket::scrollBackpack, this, _1));
 };

+ 1 - 1
client/widgets/CArtifactsOfHeroMarket.h

@@ -14,7 +14,7 @@
 class CArtifactsOfHeroMarket : public CArtifactsOfHeroBase
 {
 public:
-	std::function<void(CHeroArtPlace*)> selectArtCallback;
+	std::function<void(CArtPlace*)> selectArtCallback;
 
 	CArtifactsOfHeroMarket(const Point & position);
 	void scrollBackpack(int offset) override;

+ 46 - 12
client/widgets/CWindowWithArtifacts.cpp

@@ -23,6 +23,7 @@
 #include "../windows/CHeroWindow.h"
 #include "../windows/CSpellWindow.h"
 #include "../windows/GUIClasses.h"
+#include "../windows/CHeroBackpackWindow.h"
 #include "../CPlayerInterface.h"
 #include "../CGameInfo.h"
 
@@ -39,18 +40,19 @@ void CWindowWithArtifacts::addSet(CArtifactsOfHeroPtr artSet)
 
 void CWindowWithArtifacts::addSetAndCallbacks(CArtifactsOfHeroPtr artSet)
 {
-	CArtifactsOfHeroBase::PutBackPickedArtCallback artPutBackHandler = []() -> void
+	CArtifactsOfHeroBase::PutBackPickedArtCallback artPutBackFunctor = []() -> void
 	{
 		CCS->curh->dragAndDropCursor(nullptr);
 	};
 
 	addSet(artSet);
-	std::visit([this, artPutBackHandler](auto artSetWeak)
+	std::visit([this, artPutBackFunctor](auto artSetWeak)
 		{
 			auto artSet = artSetWeak.lock();
-			artSet->leftClickCallback = std::bind(&CWindowWithArtifacts::leftClickArtPlaceHero, this, _1, _2);
-			artSet->showPopupCallback = std::bind(&CWindowWithArtifacts::rightClickArtPlaceHero, this, _1, _2);
-			artSet->setPutBackPickedArtifactCallback(artPutBackHandler);
+			artSet->clickPressedCallback = std::bind(&CWindowWithArtifacts::clickPressedArtPlaceHero, this, _1, _2, _3);
+			artSet->showPopupCallback = std::bind(&CWindowWithArtifacts::showPopupArtPlaceHero, this, _1, _2, _3);
+			artSet->gestureCallback = std::bind(&CWindowWithArtifacts::gestureArtPlaceHero, this, _1, _2, _3);
+			artSet->setPutBackPickedArtifactCallback(artPutBackFunctor);
 		}, artSet);
 }
 
@@ -77,7 +79,7 @@ const CArtifactInstance * CWindowWithArtifacts::getPickedArtifact()
 		return nullptr;
 }
 
-void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace)
+void CWindowWithArtifacts::clickPressedArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition)
 {
 	const auto artSetWeak = findAOHbyRef(artsInst);
 	assert(artSetWeak.has_value());
@@ -85,7 +87,7 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
 	if(artPlace.isLocked())
 		return;
 
-	const auto checkSpecialArts = [](const CGHeroInstance * hero, CHeroArtPlace & artPlace) -> bool
+	const auto checkSpecialArts = [](const CGHeroInstance * hero, CArtPlace & artPlace) -> bool
 	{
 		if(artPlace.getArt()->getTypeId() == ArtifactID::SPELLBOOK)
 		{
@@ -206,10 +208,18 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
 					}
 				}
 			}
+			else if constexpr(std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroQuickBackpack>>)
+			{
+				const auto hero = artSetPtr->getHero();
+				artSetPtr->swapArtifacts(ArtifactLocation(hero->id, artPlace.slot),
+					ArtifactLocation(hero->id, artSetPtr->getFilterSlot()));
+				if(closeCallback)
+					closeCallback();
+			}
 		}, artSetWeak.value());
 }
 
-void CWindowWithArtifacts::rightClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace)
+void CWindowWithArtifacts::showPopupArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition)
 {
 	const auto artSetWeak = findAOHbyRef(artsInst);
 	assert(artSetWeak.has_value());
@@ -218,7 +228,7 @@ void CWindowWithArtifacts::rightClickArtPlaceHero(CArtifactsOfHeroBase & artsIns
 		return;
 
 	std::visit(
-		[&artPlace](auto artSetWeak) -> void
+		[&artPlace, &cursorPosition](auto artSetWeak) -> void
 		{
 			const auto artSetPtr = artSetWeak.lock();
 
@@ -239,16 +249,40 @@ void CWindowWithArtifacts::rightClickArtPlaceHero(CArtifactsOfHeroBase & artsIns
 						return;
 					}
 					if(artPlace.text.size())
-						artPlace.LRClickableAreaWTextComp::showPopupWindow(GH.getCursorPosition());
+						artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
 				}
 			}
 			// Altar window, Market window right click handler
 			else if constexpr(
 				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroAltar>> ||
-				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMarket>>)
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMarket>> ||
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroQuickBackpack>>)
 			{
 				if(artPlace.getArt() && artPlace.text.size())
-					artPlace.LRClickableAreaWTextComp::showPopupWindow(GH.getCursorPosition());
+					artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
+			}
+		}, artSetWeak.value());
+}
+
+void CWindowWithArtifacts::gestureArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition)
+{
+	const auto artSetWeak = findAOHbyRef(artsInst);
+	assert(artSetWeak.has_value());
+	if(artPlace.isLocked())
+		return;
+
+	std::visit(
+		[&artPlace, cursorPosition](auto artSetWeak) -> void
+		{
+			const auto artSetPtr = artSetWeak.lock();
+			if constexpr(
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMain>> ||
+				std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>>)
+			{
+				GH.windows().createAndPushWindow<CHeroQuickBackpackWindow>(artSetPtr->getHero(), artPlace.slot);
+				auto backpackWindow = GH.windows().topWindow<CHeroQuickBackpackWindow>();
+				backpackWindow->moveTo(cursorPosition - Point(1, 1));
+				backpackWindow->fitToScreen(15);
 			}
 		}, artSetWeak.value());
 }

+ 5 - 3
client/widgets/CWindowWithArtifacts.h

@@ -24,7 +24,8 @@ public:
 		std::weak_ptr<CArtifactsOfHeroAltar>,
 		std::weak_ptr<CArtifactsOfHeroKingdom>,
 		std::weak_ptr<CArtifactsOfHeroMain>,
-		std::weak_ptr<CArtifactsOfHeroBackpack>>;
+		std::weak_ptr<CArtifactsOfHeroBackpack>,
+		std::weak_ptr<CArtifactsOfHeroQuickBackpack>>;
 	using CloseCallback = std::function<void()>;
 
 	void addSet(CArtifactsOfHeroPtr artSet);
@@ -32,8 +33,9 @@ public:
 	void addCloseCallback(CloseCallback callback);
 	const CGHeroInstance * getHeroPickedArtifact();
 	const CArtifactInstance * getPickedArtifact();
-	void leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace);
-	void rightClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace);
+	void clickPressedArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition);
+	void showPopupArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition);
+	void gestureArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition);
 
 	void artifactRemoved(const ArtifactLocation & artLoc) override;
 	void artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc, bool withRedraw) override;

+ 14 - 2
client/widgets/MiscWidgets.cpp

@@ -121,9 +121,10 @@ void LRClickableAreaWTextComp::showPopupWindow(const Point & cursorPosition)
 }
 
 CHeroArea::CHeroArea(int x, int y, const CGHeroInstance * hero)
-	: CIntObject(LCLICK | HOVER),
+	: CIntObject(LCLICK | SHOW_POPUP | HOVER),
 	hero(hero),
-	clickFunctor(nullptr)
+	clickFunctor(nullptr),
+	clickRFunctor(nullptr)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
@@ -147,12 +148,23 @@ void CHeroArea::addClickCallback(ClickFunctor callback)
 	clickFunctor = callback;
 }
 
+void CHeroArea::addRClickCallback(ClickFunctor callback)
+{
+	clickRFunctor = callback;
+}
+
 void CHeroArea::clickPressed(const Point & cursorPosition)
 {
 	if(clickFunctor)
 		clickFunctor();
 }
 
+void CHeroArea::showPopupWindow(const Point & cursorPosition)
+{
+	if(clickRFunctor)
+		clickRFunctor();
+}
+
 void CHeroArea::hover(bool on)
 {
 	if (on && hero)

+ 3 - 0
client/widgets/MiscWidgets.h

@@ -190,12 +190,15 @@ public:
 
 	CHeroArea(int x, int y, const CGHeroInstance * hero);
 	void addClickCallback(ClickFunctor callback);
+	void addRClickCallback(ClickFunctor callback);
 	void clickPressed(const Point & cursorPosition) override;
+	void showPopupWindow(const Point & cursorPosition) override;
 	void hover(bool on) override;
 private:
 	const CGHeroInstance * hero;
 	std::shared_ptr<CAnimImage> portrait;
 	ClickFunctor clickFunctor;
+	ClickFunctor clickRFunctor;
 	ClickFunctor showPopupHandler;
 };
 

+ 6 - 3
client/widgets/TextControls.cpp

@@ -375,7 +375,7 @@ void CTextBox::setText(const std::string & text)
 	else if(slider)
 	{
 		// decrease width again if slider still used
-		label->pos.w = pos.w - 32;
+		label->pos.w = pos.w - 16;
 		assert(label->pos.w > 0);
 		label->setText(text);
 		slider->setAmount(label->textSize.y);
@@ -383,12 +383,12 @@ void CTextBox::setText(const std::string & text)
 	else if(label->textSize.y > label->pos.h)
 	{
 		// create slider and update widget
-		label->pos.w = pos.w - 32;
+		label->pos.w = pos.w - 16;
 		assert(label->pos.w > 0);
 		label->setText(text);
 
 		OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);
-		slider = std::make_shared<CSlider>(Point(pos.w - 32, 0), pos.h, std::bind(&CTextBox::sliderMoved, this, _1),
+		slider = std::make_shared<CSlider>(Point(pos.w - 16, 0), pos.h, std::bind(&CTextBox::sliderMoved, this, _1),
 			label->pos.h, label->textSize.y, 0, Orientation::VERTICAL, CSlider::EStyle(sliderStyle));
 		slider->setScrollStep((int)graphics->fonts[label->font]->getLineHeight());
 		slider->setPanningStep(1);
@@ -801,6 +801,9 @@ void CFocusable::moveFocus()
 		if(i == focusables.end())
 			i = focusables.begin();
 
+		if (*i == this)
+			return;
+
 		if((*i)->isActive())
 		{
 			(*i)->giveFocus();

+ 51 - 14
client/windows/CHeroBackpackWindow.cpp

@@ -22,25 +22,19 @@
 CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero)
 	: CWindowObject((EOptions)0)
 {
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 
-	stretchedBackground = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, 410, 425));
-
-	arts = std::make_shared<CArtifactsOfHeroBackpack>(Point(windowMargin, windowMargin));
-	arts->setHero(hero);
+	stretchedBackground = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, 0, 0));
+	arts = std::make_shared<CArtifactsOfHeroBackpack>();
+	arts->moveBy(Point(windowMargin, windowMargin));
 	addSetAndCallbacks(arts);
-
+	arts->setHero(hero);
 	addCloseCallback(std::bind(&CHeroBackpackWindow::close, this));
-
 	quitButton = std::make_shared<CButton>(Point(), AnimationPath::builtin("IOKAY32.def"), CButton::tooltip(""), [this]() { close(); }, EShortcut::GLOBAL_RETURN);
-
-	stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin;
-	stretchedBackground->pos.h = arts->pos.h + quitButton->pos.h + 3 * windowMargin;
-	pos.w = stretchedBackground->pos.w;
-	pos.h = stretchedBackground->pos.h;
+	pos.w = stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin;
+	pos.h = stretchedBackground->pos.h = arts->pos.h + quitButton->pos.h + 3 * windowMargin;
+	quitButton->moveTo(Point(pos.x + pos.w / 2 - quitButton->pos.w / 2, pos.y + arts->pos.h + 2 * windowMargin));
 	center();
-
-	quitButton->moveBy(Point(GH.screenDimensions().x / 2 - quitButton->pos.w / 2 - quitButton->pos.x, arts->pos.h + 2 * windowMargin));
 }
 
 void CHeroBackpackWindow::showAll(Canvas & to)
@@ -48,3 +42,46 @@ void CHeroBackpackWindow::showAll(Canvas & to)
 	CIntObject::showAll(to);
 	CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15);
 }
+
+CHeroQuickBackpackWindow::CHeroQuickBackpackWindow(const CGHeroInstance * hero, ArtifactPosition targetSlot)
+	: CWindowObject((EOptions)0)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
+
+	stretchedBackground = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, 0, 0));
+	arts = std::make_shared<CArtifactsOfHeroQuickBackpack>(targetSlot);
+	arts->moveBy(Point(windowMargin, windowMargin));
+	addSetAndCallbacks(static_cast<std::weak_ptr<CArtifactsOfHeroQuickBackpack>>(arts));
+	arts->setHero(hero);
+	addCloseCallback(std::bind(&CHeroQuickBackpackWindow::close, this));
+	addUsedEvents(GESTURE);
+	pos.w = stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin;
+	pos.h = stretchedBackground->pos.h = arts->pos.h + windowMargin;
+}
+
+void CHeroQuickBackpackWindow::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
+{
+	if(on)
+		return;
+
+	arts->swapSelected();
+	close();
+}
+
+void CHeroQuickBackpackWindow::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
+{
+	arts->selectSlotAt(currentPosition);
+	redraw();
+}
+
+void CHeroQuickBackpackWindow::showAll(Canvas & to)
+{
+	if(arts->getSlotsNum() == 0)
+	{
+		// Dirty solution for closing that window
+		close();
+		return;
+	}
+	CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to.getInternalSurface(), pos.w + 28, pos.h + 29, pos.x - 14, pos.y - 15);
+	CIntObject::showAll(to);
+}

+ 17 - 2
client/windows/CHeroBackpackWindow.h

@@ -19,11 +19,26 @@ class CHeroBackpackWindow : public CWindowObject, public CWindowWithArtifacts
 public:
 	CHeroBackpackWindow(const CGHeroInstance * hero);
 	
-private:
+protected:
 	std::shared_ptr<CArtifactsOfHeroBackpack> arts;
 	std::shared_ptr<CButton> quitButton;
 	std::shared_ptr<CFilledTexture> stretchedBackground;
-	const int windowMargin = 10;
+	const int windowMargin = 5;
+
+	void showAll(Canvas & to) override;
+};
+
+class CHeroQuickBackpackWindow : public CWindowObject, public CWindowWithArtifacts
+{
+public:
+	CHeroQuickBackpackWindow(const CGHeroInstance * hero, ArtifactPosition targetSlot);
+	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
+	void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
+
+private:
+	std::shared_ptr<CArtifactsOfHeroQuickBackpack> arts;
+	std::shared_ptr<CFilledTexture> stretchedBackground;
+	const int windowMargin = 5;
 
 	void showAll(Canvas & to) override;
 };

+ 0 - 1
client/windows/CHeroWindow.h

@@ -111,6 +111,5 @@ public:
 	void createBackpackWindow();
 
 	//friends
-	friend void CHeroArtPlace::clickPressed(const Point & cursorPosition);
 	friend class CPlayerInterface;
 };

+ 3 - 3
client/windows/CMapOverview.cpp

@@ -13,8 +13,6 @@
 
 #include "../lobby/SelectionTab.h"
 
-#include <vstd/DateUtils.h>
-
 #include "../gui/CGuiHandler.h"
 #include "../gui/WindowHandler.h"
 #include "../widgets/CComponent.h"
@@ -29,6 +27,7 @@
 #include "../render/Graphics.h"
 
 #include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/TextOperations.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/campaign/CampaignState.h"
 #include "../../lib/mapping/CMap.h"
@@ -42,6 +41,7 @@
 #include "../../lib/serializer/CLoadFile.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/rmg/CMapGenOptions.h"
+#include "../../lib/Languages.h"
 
 CMapOverview::CMapOverview(std::string mapName, std::string fileName, std::string date, ResourcePath resource, ESelectionScreen tabType)
 	: CWindowObject(BORDERED | RCLICK_POPUP), resource(resource), mapName(mapName), fileName(fileName), date(date), tabType(tabType)
@@ -199,7 +199,7 @@ CMapOverviewWidget::CMapOverviewWidget(CMapOverview& parent):
 		if(p.date.empty())
 		{
 			std::time_t time = boost::filesystem::last_write_time(*CResourceHandler::get()->getResourceName(ResourcePath(p.resource.getName(), p.tabType == ESelectionScreen::campaignList ? EResType::CAMPAIGN : EResType::MAP)));
-			w->setText(vstd::getFormattedDateTime(time));
+			w->setText(TextOperations::getFormattedDateTimeLocal(time));
 		}
 		else
 			w->setText(p.date);

+ 114 - 62
client/windows/CSpellWindow.cpp

@@ -124,70 +124,22 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
 		offL = offR = offT = offB = offRM = 0;
 		spellsPerPage = 12;
 	}
-	pos = background->center(Point(pos.w/2 + pos.x, pos.h/2 + pos.y));
-
-	//initializing castable spells
-	mySpells.reserve(CGI->spellh->objects.size());
-	for(const CSpell * spell : CGI->spellh->objects)
-	{
-		if(!spell->isCreatureAbility() && myHero->canCastThisSpell(spell))
-			mySpells.push_back(spell);
-	}
-	std::sort(mySpells.begin(), mySpells.end(), spellsorter);
-
-	//initializing sizes of spellbook's parts
-	for(auto & elem : sitesPerTabAdv)
-		elem = 0;
-	for(auto & elem : sitesPerTabBattle)
-		elem = 0;
-
-	for(const auto spell : mySpells)
-	{
-		int * sitesPerOurTab = spell->isCombat() ? sitesPerTabBattle : sitesPerTabAdv;
-
-		++sitesPerOurTab[4];
-
-		spell->forEachSchool([&sitesPerOurTab](const SpellSchool & school, bool & stop)
-		{
-			++sitesPerOurTab[school];
-		});
-	}
-	if(sitesPerTabAdv[4] % spellsPerPage == 0)
-		sitesPerTabAdv[4]/=spellsPerPage;
-	else
-		sitesPerTabAdv[4] = sitesPerTabAdv[4]/spellsPerPage + 1;
-
-	for(int v=0; v<4; ++v)
-	{
-		if(sitesPerTabAdv[v] <= spellsPerPage - 2)
-			sitesPerTabAdv[v] = 1;
-		else
-		{
-			if((sitesPerTabAdv[v] - spellsPerPage - 2) % spellsPerPage == 0)
-				sitesPerTabAdv[v] = (sitesPerTabAdv[v] - spellsPerPage - 2) / spellsPerPage + 1;
-			else
-				sitesPerTabAdv[v] = (sitesPerTabAdv[v] - spellsPerPage - 2) / spellsPerPage + 2;
-		}
-	}
 
-	if(sitesPerTabBattle[4] % spellsPerPage == 0)
-		sitesPerTabBattle[4]/=spellsPerPage;
-	else
-		sitesPerTabBattle[4] = sitesPerTabBattle[4]/spellsPerPage + 1;
+	pos = background->center(Point(pos.w/2 + pos.x, pos.h/2 + pos.y));
 
-	for(int v=0; v<4; ++v)
+	if(settings["general"]["enableUiEnhancements"].Bool())
 	{
-		if(sitesPerTabBattle[v] <= spellsPerPage - 2)
-			sitesPerTabBattle[v] = 1;
-		else
-		{
-			if((sitesPerTabBattle[v] - spellsPerPage - 2) % spellsPerPage == 0)
-				sitesPerTabBattle[v] = (sitesPerTabBattle[v] - spellsPerPage - 2) / spellsPerPage + 1;
-			else
-				sitesPerTabBattle[v] = (sitesPerTabBattle[v] - spellsPerPage - 2) / spellsPerPage + 2;
-		}
+		Rect r(90, isBigSpellbook ? 480 : 420, isBigSpellbook ? 160 : 110, 16);
+		const ColorRGBA rectangleColor = ColorRGBA(0, 0, 0, 75);
+		const ColorRGBA borderColor = ColorRGBA(128, 100, 75);
+		const ColorRGBA grayedColor = ColorRGBA(158, 130, 105);
+		searchBoxRectangle = std::make_shared<TransparentFilledRectangle>(r.resize(1), rectangleColor, borderColor);
+		searchBoxDescription = std::make_shared<CLabel>(r.center().x, r.center().y, FONT_SMALL, ETextAlignment::CENTER, grayedColor, CGI->generaltexth->translate("vcmi.spellBook.search"));
+
+		searchBox = std::make_shared<CTextInput>(r, FONT_SMALL, std::bind(&CSpellWindow::searchInput, this));
 	}
 
+	processSpells();
 
 	//numbers of spell pages computed
 
@@ -253,7 +205,7 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
 
 	selectedTab = battleSpellsOnly ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap;
 	schoolTab->setFrame(selectedTab, 0);
-	int cp = battleSpellsOnly ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbokLastPageAdvmap;
+	int cp = battleSpellsOnly ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap;
 	// spellbook last page battle index is not reset after battle, so this needs to stay here
 	vstd::abetween(cp, 0, std::max(0, pagesWithinCurrentTab() - 1));
 	setCurrentPage(cp);
@@ -292,14 +244,114 @@ std::shared_ptr<IImage> CSpellWindow::createBigSpellBook()
 	Canvas tmp5 = Canvas(Point(409, 141));
 	tmp5.draw(img, Point(0, 0), Rect(100, 38 + 45, 509 - 15, 400 - 38));
 	canvas.drawScaled(tmp5, Point(90, 45), Point(615, 415));
+	// carpet
+	Canvas tmp6 = Canvas(Point(590, 59));
+	tmp6.draw(img, Point(0, 0), Rect(15, 484, 590, 59));
+	canvas.drawScaled(tmp6, Point(0, 545), Point(800, 59));
+	// remove bookmarks
+	for (int i = 0; i < 56; i++)
+		canvas.draw(Canvas(canvas, Rect(i < 30 ? 268 : 327, 464, 1, 46)), Point(269 + i, 464));
+	for (int i = 0; i < 56; i++)
+		canvas.draw(Canvas(canvas, Rect(469, 464, 1, 42)), Point(470 + i, 464));
+	for (int i = 0; i < 57; i++)
+		canvas.draw(Canvas(canvas, Rect(i < 30 ? 564 : 630, 464, 1, 44)), Point(565 + i, 464));
+	for (int i = 0; i < 56; i++)
+		canvas.draw(Canvas(canvas, Rect(656, 464, 1, 47)), Point(657 + i, 464));
+	// draw bookmarks
+	canvas.draw(img, Point(278, 464), Rect(220, 405, 37, 47));
+	canvas.draw(img, Point(481, 465), Rect(354, 406, 37, 41));
+	canvas.draw(img, Point(575, 465), Rect(417, 406, 37, 45));
+	canvas.draw(img, Point(667, 465), Rect(478, 406, 37, 47));
 
 	return GH.renderHandler().createImage(canvas.getInternalSurface());
 }
 
+void CSpellWindow::searchInput()
+{
+	if(searchBox)
+		searchBoxDescription->setEnabled(searchBox->getText().empty());
+
+	processSpells();
+
+	int cp = 0;
+	// spellbook last page battle index is not reset after battle, so this needs to stay here
+	vstd::abetween(cp, 0, std::max(0, pagesWithinCurrentTab() - 1));
+	setCurrentPage(cp);
+	computeSpellsPerArea();
+}
+
+void CSpellWindow::processSpells()
+{
+	mySpells.clear();
+
+	//initializing castable spells
+	mySpells.reserve(CGI->spellh->objects.size());
+	for(const CSpell * spell : CGI->spellh->objects)
+	{
+		bool searchTextFound = !searchBox || boost::algorithm::contains(boost::algorithm::to_lower_copy(spell->getNameTranslated()), boost::algorithm::to_lower_copy(searchBox->getText()));
+		if(!spell->isCreatureAbility() && myHero->canCastThisSpell(spell) && searchTextFound)
+			mySpells.push_back(spell);
+	}
+	std::sort(mySpells.begin(), mySpells.end(), spellsorter);
+
+	//initializing sizes of spellbook's parts
+	for(auto & elem : sitesPerTabAdv)
+		elem = 0;
+	for(auto & elem : sitesPerTabBattle)
+		elem = 0;
+
+	for(const auto spell : mySpells)
+	{
+		int * sitesPerOurTab = spell->isCombat() ? sitesPerTabBattle : sitesPerTabAdv;
+
+		++sitesPerOurTab[4];
+
+		spell->forEachSchool([&sitesPerOurTab](const SpellSchool & school, bool & stop)
+		{
+			++sitesPerOurTab[school];
+		});
+	}
+	if(sitesPerTabAdv[4] % spellsPerPage == 0)
+		sitesPerTabAdv[4]/=spellsPerPage;
+	else
+		sitesPerTabAdv[4] = sitesPerTabAdv[4]/spellsPerPage + 1;
+
+	for(int v=0; v<4; ++v)
+	{
+		if(sitesPerTabAdv[v] <= spellsPerPage - 2)
+			sitesPerTabAdv[v] = 1;
+		else
+		{
+			if((sitesPerTabAdv[v] - spellsPerPage - 2) % spellsPerPage == 0)
+				sitesPerTabAdv[v] = (sitesPerTabAdv[v] - spellsPerPage - 2) / spellsPerPage + 1;
+			else
+				sitesPerTabAdv[v] = (sitesPerTabAdv[v] - spellsPerPage - 2) / spellsPerPage + 2;
+		}
+	}
+
+	if(sitesPerTabBattle[4] % spellsPerPage == 0)
+		sitesPerTabBattle[4]/=spellsPerPage;
+	else
+		sitesPerTabBattle[4] = sitesPerTabBattle[4]/spellsPerPage + 1;
+
+	for(int v=0; v<4; ++v)
+	{
+		if(sitesPerTabBattle[v] <= spellsPerPage - 2)
+			sitesPerTabBattle[v] = 1;
+		else
+		{
+			if((sitesPerTabBattle[v] - spellsPerPage - 2) % spellsPerPage == 0)
+				sitesPerTabBattle[v] = (sitesPerTabBattle[v] - spellsPerPage - 2) / spellsPerPage + 1;
+			else
+				sitesPerTabBattle[v] = (sitesPerTabBattle[v] - spellsPerPage - 2) / spellsPerPage + 2;
+		}
+	}
+}
+
 void CSpellWindow::fexitb()
 {
 	(myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap) = selectedTab;
-	(myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbokLastPageAdvmap) = currentPage;
+	(myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap) = currentPage;
 
 	close();
 }
@@ -595,7 +647,7 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
 			auto guard = vstd::makeScopeGuard([this]()
 			{
 				owner->myInt->localState->spellbookSettings.spellbookLastTabAdvmap = owner->selectedTab;
-				owner->myInt->localState->spellbookSettings.spellbokLastPageAdvmap = owner->currentPage;
+				owner->myInt->localState->spellbookSettings.spellbookLastPageAdvmap = owner->currentPage;
 			});
 
 			if(mySpell->getTargetType() == spells::AimType::LOCATION)

+ 8 - 0
client/windows/CSpellWindow.h

@@ -26,6 +26,8 @@ class CLabel;
 class CGStatusBar;
 class CPlayerInterface;
 class CSpellWindow;
+class CTextInput;
+class TransparentFilledRectangle;
 
 /// The spell window
 class CSpellWindow : public CWindowObject
@@ -80,6 +82,10 @@ class CSpellWindow : public CWindowObject
 
 	std::vector<std::shared_ptr<InteractiveArea>> interactiveAreas;
 
+	std::shared_ptr<CTextInput> searchBox;
+	std::shared_ptr<TransparentFilledRectangle> searchBoxRectangle;
+	std::shared_ptr<CLabel> searchBoxDescription;
+
 	bool isBigSpellbook;
 	int spellsPerPage;
 	int offL;
@@ -99,6 +105,8 @@ class CSpellWindow : public CWindowObject
 	const CGHeroInstance * myHero; //hero whose spells are presented
 	CPlayerInterface * myInt;
 
+	void processSpells();
+	void searchInput();
 	void computeSpellsPerArea(); //recalculates spellAreas::mySpell
 
 	void setCurrentPage(int value);

+ 1 - 1
client/windows/CTradeWindow.cpp

@@ -331,7 +331,7 @@ void CTradeWindow::setMode(EMarketMode Mode)
 	}
 }
 
-void CTradeWindow::artifactSelected(CHeroArtPlace *slot)
+void CTradeWindow::artifactSelected(CArtPlace * slot)
 {
 	assert(mode == EMarketMode::ARTIFACT_RESOURCE);
 	items[1][0]->setArtInstance(slot->getArt());

+ 1 - 1
client/windows/CTradeWindow.h

@@ -40,7 +40,7 @@ public:
 	void getPositionsFor(std::vector<Rect> &poss, bool Left, EType type) const;
 	void setMode(EMarketMode Mode); //mode setter
 
-	void artifactSelected(CHeroArtPlace *slot); //used when selling artifacts -> called when user clicked on artifact slot
+	void artifactSelected(CArtPlace * slot); //used when selling artifacts -> called when user clicked on artifact slot
 	virtual void selectionChanged(bool side) = 0; //true == left
 	virtual Point selectionOffset(bool Left) const = 0;
 	virtual std::string updateSlotSubtitle(bool Left) const = 0;

+ 3 - 1
client/windows/GUIClasses.cpp

@@ -409,7 +409,9 @@ CLevelWindow::CLevelWindow(const CGHeroInstance * hero, PrimarySkill pskill, std
 		box = std::make_shared<CComponentBox>(comps, Rect(75, 300, pos.w - 150, 100));
 	}
 
-	portrait = std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsLarge"), hero->getIconIndex(), 0, 170, 66);
+	portrait = std::make_shared<CHeroArea>(170, 66, hero);
+	portrait->addClickCallback(nullptr);
+	portrait->addRClickCallback([hero](){ GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(hero)); });
 	ok = std::make_shared<CButton>(Point(297, 413), AnimationPath::builtin("IOKAY"), CButton::tooltip(), std::bind(&CLevelWindow::close, this), EShortcut::GLOBAL_ACCEPT);
 
 	//%s has gained a level.

+ 2 - 1
client/windows/GUIClasses.h

@@ -35,6 +35,7 @@ class CGStatusBar;
 class CTextBox;
 class CGarrisonInt;
 class CGarrisonSlot;
+class CHeroArea;
 
 enum class EUserEvent;
 
@@ -129,7 +130,7 @@ public:
 /// Raised up level window where you can select one out of two skills
 class CLevelWindow : public CWindowObject
 {
-	std::shared_ptr<CAnimImage> portrait;
+	std::shared_ptr<CHeroArea> portrait;
 	std::shared_ptr<CButton> ok;
 	std::shared_ptr<CLabel> mainTitle;
 	std::shared_ptr<CLabel> levelTitle;

+ 7 - 0
client/windows/settings/AdventureOptionsTab.cpp

@@ -130,6 +130,10 @@ AdventureOptionsTab::AdventureOptionsTab()
 	{
 		return setBoolSetting("adventure", "smoothDragging", value);
 	});
+	addCallback("skipAdventureMapAnimationsChanged", [](bool value)
+	{
+		return setBoolSetting("gameTweaks", "skipAdventureMapAnimations", value);
+	});
 	build(config);
 
 	std::shared_ptr<CToggleGroup> playerHeroSpeedToggle = widget<CToggleGroup>("heroMovementSpeedPicker");
@@ -172,4 +176,7 @@ AdventureOptionsTab::AdventureOptionsTab()
 	std::shared_ptr<CToggleButton> smoothDraggingCheckbox = widget<CToggleButton>("smoothDraggingCheckbox");
 	if (smoothDraggingCheckbox)
 		smoothDraggingCheckbox->setSelected(settings["adventure"]["smoothDragging"].Bool());
+
+	std::shared_ptr<CToggleButton> skipAdventureMapAnimationsCheckbox = widget<CToggleButton>("skipAdventureMapAnimationsCheckbox");
+	skipAdventureMapAnimationsCheckbox->setSelected(settings["gameTweaks"]["skipAdventureMapAnimations"].Bool());
 }

+ 11 - 1
client/windows/settings/GeneralOptionsTab.cpp

@@ -162,9 +162,15 @@ GeneralOptionsTab::GeneralOptionsTab()
 		setBoolSetting("general", "enableUiEnhancements", value);
 	});
 
-	addCallback("enableLargeSpellbookChanged", [](bool value)
+	addCallback("enableLargeSpellbookChanged", [this](bool value)
 	{
 		setBoolSetting("gameTweaks", "enableLargeSpellbook", value);
+		std::shared_ptr<CToggleButton> spellbookAnimationCheckbox = widget<CToggleButton>("spellbookAnimationCheckbox");
+		if(value)
+			spellbookAnimationCheckbox->disable();
+		else
+			spellbookAnimationCheckbox->enable();
+		redraw();
 	});
 
 	addCallback("audioMuteFocusChanged", [](bool value)
@@ -196,6 +202,10 @@ GeneralOptionsTab::GeneralOptionsTab()
 
 	std::shared_ptr<CToggleButton> spellbookAnimationCheckbox = widget<CToggleButton>("spellbookAnimationCheckbox");
 	spellbookAnimationCheckbox->setSelected(settings["video"]["spellbookAnimation"].Bool());
+	if(settings["gameTweaks"]["enableLargeSpellbook"].Bool())
+		spellbookAnimationCheckbox->disable();
+	else
+		spellbookAnimationCheckbox->enable();
 
 	std::shared_ptr<CToggleButton> fullscreenBorderlessCheckbox = widget<CToggleButton>("fullscreenBorderlessCheckbox");
 	if (fullscreenBorderlessCheckbox)

+ 1 - 1
cmake_modules/VersionDefinition.cmake

@@ -1,6 +1,6 @@
 set(VCMI_VERSION_MAJOR 1)
 set(VCMI_VERSION_MINOR 4)
-set(VCMI_VERSION_PATCH 1)
+set(VCMI_VERSION_PATCH 2)
 add_definitions(
 	-DVCMI_VERSION_MAJOR=${VCMI_VERSION_MAJOR}
 	-DVCMI_VERSION_MINOR=${VCMI_VERSION_MINOR}

+ 2 - 0
config/artifacts.json

@@ -1873,6 +1873,7 @@
 			}
 		],
 		"index" : 127,
+		"class" : "SPECIAL",
 		"type" : ["HERO"]
 	},
 	"armageddonsBlade":
@@ -1917,6 +1918,7 @@
 			}
 		],
 		"index" : 128,
+		"class" : "SPECIAL",
 		"type" : ["HERO"]
 	},
 	"angelicAlliance":

+ 7 - 2
config/schemas/settings.json

@@ -582,7 +582,8 @@
 				"infoBarPick",
 				"skipBattleIntroMusic",
 				"infoBarCreatureManagement",
-				"enableLargeSpellbook"
+				"enableLargeSpellbook",
+				"skipAdventureMapAnimations"
 			],
 			"properties" : {
 				"showGrid" : {
@@ -615,9 +616,13 @@
 				},
 				"infoBarCreatureManagement": {
 					"type" : "boolean",
-					"default" : false
+					"default" : true
 				},
 				"enableLargeSpellbook" : {
+					"type": "boolean",
+					"default": true
+				},
+				"skipAdventureMapAnimations": {
 					"type": "boolean",
 					"default": false
 				}

+ 5 - 2
config/spells/timed.json

@@ -1153,7 +1153,10 @@
 		},
 		"targetCondition" : {
 			"noneOf" : {
-				"bonus.SIEGE_WEAPON" : "absolute"
+				"bonus.MIND_IMMUNITY" : "absolute",
+				"bonus.NON_LIVING" : "absolute",
+				"bonus.SIEGE_WEAPON" : "absolute",
+				"bonus.UNDEAD" : "absolute"
 			}
 		}
 	},
@@ -1221,7 +1224,7 @@
 				"effects" : {
 					"attacksNearestCreature" : {
 						"type" : "ATTACKS_NEAREST_CREATURE",
-						"duration" : "UNTIL_ATTACK"
+						"duration" : "UNTIL_OWN_ATTACK"
 					}
 				}
 			},

+ 2 - 1
config/terrainViewPatterns.json

@@ -85,7 +85,8 @@
 				"N", "N", "N",
 				"N", "N", "N"
 			],
-			"mapping" : { "normal" : "49-72", "dirt" : "21-44", "sand" : "0-23", "water" : "20-32", "rock": "0-7", "hota" : "77-117" }
+			"decoration" : true,
+			"mapping" : { "normal" : "49-56,57-72", "dirt" : "21-28,29-44", "sand" : "0-11,12-23", "water" : "20-32", "rock": "0-7", "hota" : "77-101,102-117" }
 		},
 		// Mixed transitions
 		{

+ 14 - 6
config/widgets/settings/adventureOptionsTab.json

@@ -279,7 +279,7 @@
 			"callback": "mapScrollSpeedChanged"
 		},
 		
-/////////////////////////////////////// Right section - Original H3 options
+/////////////////////////////////////// Right section - Original H3 options + some custom
 		{
 			"type" : "verticalLayout",
 			"customType" : "labelDescription",
@@ -294,6 +294,9 @@
 				},
 				{
 					"text": "core.genrltxt.574" // quick combat
+				},
+				{
+					"text": "vcmi.adventureOptions.showGrid.hover"
 				}
 			]
 		},
@@ -305,7 +308,7 @@
 			[
 				{
 					"name": "showMovePathPlaceholder",
-					"type": "checkboxFake",
+					"type": "checkboxFake"
 				},
 				{
 					"name": "heroReminderCheckbox",
@@ -317,6 +320,11 @@
 					"help": "core.help.362",
 					"callback": "quickCombatChanged"
 				},
+				{
+					"name": "showGridCheckbox",
+					"help": "vcmi.adventureOptions.showGrid",
+					"callback": "showGridChanged"
+				}
 			]
 		},
 /////////////////////////////////////// Bottom section - VCMI Options
@@ -333,7 +341,7 @@
 					"text": "vcmi.adventureOptions.forceMovementInfo.hover"
 				},
 				{
-					"text": "vcmi.adventureOptions.showGrid.hover"
+					"text": "vcmi.adventureOptions.skipAdventureMapAnimations.hover"
 				},
 				{
 					"text": "vcmi.adventureOptions.infoBarPick.hover"
@@ -370,9 +378,9 @@
 					"callback": "forceMovementInfoChanged"
 				},
 				{
-					"name": "showGridCheckbox",
-					"help": "vcmi.adventureOptions.showGrid",
-					"callback": "showGridChanged"
+					"name": "skipAdventureMapAnimationsCheckbox",
+					"help": "vcmi.adventureOptions.skipAdventureMapAnimations",
+					"callback": "skipAdventureMapAnimationsChanged"
 				},
 				{
 					"name": "infoBarPickCheckbox",

+ 29 - 0
config/widgets/settings/generalOptionsTab.json

@@ -70,6 +70,35 @@
 				}
 			]
 		},
+		{
+			"type" : "verticalLayout",
+			"customType" : "checkboxFake",
+			"position" : {"x": 10, "y": 83},
+			"items" : [
+				{
+					"created" : "desktop"
+				},
+				{},
+				{
+					"created" : "desktop"
+				},
+				{
+					"created" : "desktop"
+				},
+				{},
+				{},
+				{
+					"name": "spellbookAnimationCheckboxPlaceholder"
+				},
+				{
+					"created" : "touchscreen"
+				},
+				{
+					"created" : "mobile"
+				},
+				{}
+			]
+		},
 		{
 			"type" : "verticalLayout",
 			"customType" : "checkbox",

+ 0 - 34
config/widgets/turnTimer.json

@@ -1,34 +0,0 @@
-{
-	"items":
-	[
-		{ //backgound color
-			"type": "drawRect",
-			"rect": {"x": 4, "y": 4, "w": 72, "h": 24},
-			"color": [10, 10, 10, 255]
-		},
-
-		{ //clocks icon
-			"type": "image",
-			"image": "VCMI/BATTLEQUEUE/STATESSMALL",
-			"frame": 1,
-			"position": {"x": 4, "y": 6}
-		},
-
-		{ //timer field label
-			"name": "timer",
-			"type": "label",
-			"font": "big",
-			"alignment": "left",
-			"color": "yellow",
-			"text": "",
-			"position": {"x": 26, "y": 2}
-		},
-	],
-
-	"variables":
-	{
-		"notificationTime": [0, 1, 2, 3, 4, 5, 20],
-		"notificationSound": "WE5",
-		"textColorFromPlayerColor": true
-	}
-}

+ 6 - 0
debian/changelog

@@ -1,3 +1,9 @@
+vcmi (1.4.2) jammy; urgency=medium
+
+  * New upstream release
+
+ -- Ivan Savenko <[email protected]>  Mon, 25 Dec 2023 16:00:00 +0200
+
 vcmi (1.4.1) jammy; urgency=medium
 
   * New upstream release

+ 1 - 0
docs/Readme.md

@@ -1,6 +1,7 @@
 [![GitHub](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg)](https://github.com/vcmi/vcmi/actions/workflows/github.yml)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.0)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.1/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.1)
+[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.2/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.2)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
 
 # VCMI Project

+ 2 - 1
docs/modders/Bonus/Bonus_Duration_Types.md

@@ -13,4 +13,5 @@ Bonus may have any of these durations. They acts in disjunction.
 -   UNTIL_BEING_ATTACKED: removed after any damage-inflicting attack
 -   UNTIL_ATTACK: removed after attack and counterattacks are performed
 -   STACK_GETS_TURN: removed when stack gets its turn - used for defensive stance
--   COMMANDER_KILLED
+-   COMMANDER_KILLED
+-   UNTIL_OWN_ATTACK: removed after attack (not counterattack) is performed

+ 15 - 3
docs/players/Cheat_Codes.md

@@ -36,7 +36,7 @@ Gives specific creature in every slot, with optional amount. Examples:
 
 ### Movement points
 
-`nwcnebuchadnezzar` or `vcminahar` or `vcmimove` - give 1000000 movement points and free ship boarding for 1 day  
+`nwcnebuchadnezzar` or `vcminahar` or `vcmimove` - give unlimited (or specified amount of) movement points and free ship boarding
 Alternative usage: `vcmimove <amount>` - gives specified amount of movement points
 
 ### Resources
@@ -57,10 +57,22 @@ Alternative usage: `vcmilevel <amount>` - advances hero by specified number of l
 - `vcmiolorin` or `vcmiexp` - gives selected hero 10000 experience
 Alternative usage: `vcmiexp <amount>` - gives selected hero specified amount of experience
 
+### Luck and morale
+
+`nwcfollowthewhiterabbit` or `vcmiluck` - the currently selected hero permanently gains maximum luck
+`nwcmorpheus` or `vcmimorale` - the currently selected hero permanently gains maximum morale
+
+### Puzzle map
+
+`nwcoracle` or `vcmiobelisk` - reveals the puzzle map
+
 ### Finishing the game
 
-`nwcredpill` or `vcmisilmaril` or `vcmiwin` - player wins  
-`nwcbluepill` or `vcmimelkor` or `vcmilose` - player loses  
+`nwcredpill` or `vcmisilmaril` or `vcmiwin` - player wins
+`nwcbluepill` or `vcmimelkor` or `vcmilose` - player loses
+
+### Misc
+`nwctheone` or `vcmigod` - reveals the whole map, gives 5 archangels in each empty slot, unlimited movement points and permanent flight
 
 ## Using cheat codes on other players
 By default, all cheat codes apply to current player. Alternatively, it is possible to specify player that you want to target:

+ 1 - 1
include/vstd/DateUtils.h

@@ -5,7 +5,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 namespace vstd
 {
 
-	DLL_LINKAGE std::string getFormattedDateTime(std::time_t dt);
+	DLL_LINKAGE std::string getFormattedDateTime(std::time_t dt, std::string format);
 	DLL_LINKAGE std::string getDateTimeISO8601Basic(std::time_t dt);
 
 }

+ 61 - 37
launcher/eu.vcmi.VCMI.metainfo.xml

@@ -1,56 +1,94 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<component type="desktop-application">
+<?xml version='1.0' encoding='utf-8'?>
+<component type="desktop">
 	<id>eu.vcmi.VCMI</id>
-	<metadata_license>CC-BY-SA-4.0</metadata_license>
-	<project_license>GPL-2.0-or-later</project_license>
 	<name>VCMI</name>
 	<summary>Open-source game engine for Heroes of Might and Magic III</summary>
+	<summary xml:lang="cs">Herní engine s otevřeným kódem pro Heroes of Might and Magic III</summary>
+	<summary xml:lang="de">Open-Source-Spielengine für Heroes of Might and Magic III</summary>
+	<developer_name>VCMI Team</developer_name>
+	<developer_name xml:lang="cs">Tým VCMI</developer_name>
+	<metadata_license>CC-BY-SA-4.0</metadata_license>
+	<project_license>GPL-2.0-or-later</project_license>
 	<description>
 		<p>VCMI is an open-source engine for Heroes of Might and Magic III with new possibilities. Years of intensive work resulted in an impressive amount of features. Among the current features are:</p>
+		<p xml:lang="cs">VCMI je engine s otevřeným kódem a novými možnostmi pro Heroes of Might and Magic III. Roky usilovné práce vyústily v úchvatném počtu nových funkcí. Mezi současnými funkcemi jsou:</p>
+		<p xml:lang="de">VCMI ist eine Open-Source-Engine für Heroes of Might and Magic III mit neuen Möglichkeiten. Jahrelange intensive Arbeit führte zu einer beeindruckenden Anzahl von Features. Zu den aktuellen Features gehören:</p>
 		<ul>
 			<li>Complete gameplay mechanics</li>
+			<li xml:lang="cs">Kompletní herní mechaniky</li>
+			<li xml:lang="de">Vollständige Spielmechanik</li>
 			<li>Almost all objects, abilities, spells and other content</li>
+			<li xml:lang="cs">Skoro všechny předměty, schopnosti, kouzla a ostatní obsah</li>
+			<li xml:lang="de">Fast alle Objekte, Fähigkeiten, Zaubersprüche und andere Inhalte</li>
 			<li>Basic battle AI and adventure AI</li>
+			<li xml:lang="cs">Základní AI boje a mapy světa</li>
+			<li xml:lang="de">Grundlegende Kampf- und Abenteuer-KI</li>
 			<li>Many GUI improvements: high resolutions, stack queue, creature window</li>
+			<li xml:lang="cs">Mnoho vylepšení rozhraní: vyšší rozlišení, fronta oddílů a okno bojovníků</li>
+			<li xml:lang="de">Viele GUI-Verbesserungen: Hohe Auflösungen, Truppenwarteschlange, Kreaturenfenster</li>
 			<li>Advanced and easy mod support - add new towns, creatures, heroes, artifacts and spells without limits or conflicts</li>
+			<li xml:lang="cs">Pokročilá a jednoduchá podpora modifikací - přidání nových měst, bojovníků, hrdinů, artefaktů a kouzel bez limitů a konfliktů</li>
+			<li xml:lang="de">Erweiterte und einfache Mod-Unterstützung - füge neue Städte, Kreaturen, Helden, Artefakte und Zaubersprüche ohne Einschränkungen oder Konflikte hinzu</li>
 			<li>Launcher for easy configuration - download mods from our server and install them immediately!</li>
+			<li xml:lang="cs">Spouštěč pro jednoduché nastavení - stahujte modifikace z našeho serveru a hned je instalujte!</li>
+			<li xml:lang="de">Launcher für einfache Konfiguration - Mods von unserem Server herunterladen und sofort installieren!</li>
 			<li>Random map generator that supports objects added by mods</li>
+			<li xml:lang="cs">Náhodný generátor map, který podporuje předměty přidané modifikacemi</li>
+			<li xml:lang="de">Zufallsgenerator für Karten, der von Mods hinzugefügte Objekte unterstützt</li>
 		</ul>
 		<p>Note: In order to play the game using VCMI you need to own data files for Heroes of Might and Magic III: The Shadow of Death.</p>
+		<p xml:lang="cs">Poznámka: pokud chcete hrát hru přes VCMI, musíte vlastnit datové soubory Heroes of Might and Magic III: The Shadow of Death.</p>
+		<p xml:lang="de">Hinweis: Um das Spiel mit VCMI spielen zu können, sind die Originaldateien für Heroes of Might and Magic III: The Shadow of Death erforderlich.</p>
 		<p>If you want help, please check our forum, bug tracker or GitHub page.</p>
+		<p xml:lang="cs">Pokud chcete pomoct, prosíme, podívejte se na naše fórum nebo GitHub.</p>
+		<p xml:lang="de">Wird Hilfe benötigt, besucht bitte unser Forum, den Bugtracker oder die GitHub-Seite.</p>
 	</description>
 	<screenshots>
 		<screenshot type="default">
-			<image>https://play-lh.googleusercontent.com/-ZErg8hUyc3TScVQZYh30KzaUDWmnIGRcN3WPCQimdviOJVoMkjDdIW19WqX0KZO7mk=w2560-h1440</image>
+			<image type="source">https://play-lh.googleusercontent.com/-ZErg8hUyc3TScVQZYh30KzaUDWmnIGRcN3WPCQimdviOJVoMkjDdIW19WqX0KZO7mk=w2560-h1440</image>
 		</screenshot>
 		<screenshot>
-			<image>https://play-lh.googleusercontent.com/8Uuuir7_0pDCPohacS7nd6CQuvaOO7nKmKWo-A-EysRY8uIK5qLsv0cpMxT3AFu2DA=w2560-h1440</image>
+			<image type="source">https://play-lh.googleusercontent.com/8Uuuir7_0pDCPohacS7nd6CQuvaOO7nKmKWo-A-EysRY8uIK5qLsv0cpMxT3AFu2DA=w2560-h1440</image>
 		</screenshot>
 		<screenshot>
-			<image>https://play-lh.googleusercontent.com/993tSmsQh1qgb1_XQUxD1wmS8Zuiydf0LsQK_nU2fAiJl62n0_Vl_5JMVK-J9lvVeQ=w2560-h1440</image>
+			<image type="source">https://play-lh.googleusercontent.com/993tSmsQh1qgb1_XQUxD1wmS8Zuiydf0LsQK_nU2fAiJl62n0_Vl_5JMVK-J9lvVeQ=w2560-h1440</image>
 		</screenshot>
 		<screenshot>
-			<image>https://play-lh.googleusercontent.com/N3fn4cqX2iN0t9jzEo0vgEMBpUYWyRgOz8AtDsMLmF-BTyxO4l2uoAderEIhXPvPsg=w2560-h1440</image>
+			<image type="source">https://play-lh.googleusercontent.com/N3fn4cqX2iN0t9jzEo0vgEMBpUYWyRgOz8AtDsMLmF-BTyxO4l2uoAderEIhXPvPsg=w2560-h1440</image>
 		</screenshot>
 		<screenshot>
-			<image>https://play-lh.googleusercontent.com/9wSQsPlMPSBAzekEcw1OVpzeOvYiHrYRYHgpa1aRk31pvR752gu_cUZws5x8JMCACw=w2560-h1440</image>
+			<image type="source">https://play-lh.googleusercontent.com/9wSQsPlMPSBAzekEcw1OVpzeOvYiHrYRYHgpa1aRk31pvR752gu_cUZws5x8JMCACw=w2560-h1440</image>
 		</screenshot>
 		<screenshot>
-			<image>https://play-lh.googleusercontent.com/nU_MSwmEhiQ14Sx9xI3QFF1FhL7BYTDMbEYQg-XglSYwIMpYZDpP92_ybTtu4cVJSxo=w2560-h1440</image>
+			<image type="source">https://play-lh.googleusercontent.com/nU_MSwmEhiQ14Sx9xI3QFF1FhL7BYTDMbEYQg-XglSYwIMpYZDpP92_ybTtu4cVJSxo=w2560-h1440</image>
 		</screenshot>
 		<screenshot>
-			<image>https://play-lh.googleusercontent.com/rhI5moPCKQpYdjnRaJJ_CdIxSAXUkrH7ddghuPWaW0vcyaltd-ZGZG4Kl6v3YKGVlzU=w2560-h1440</image>
+			<image type="source">https://play-lh.googleusercontent.com/rhI5moPCKQpYdjnRaJJ_CdIxSAXUkrH7ddghuPWaW0vcyaltd-ZGZG4Kl6v3YKGVlzU=w2560-h1440</image>
 		</screenshot>
 		<screenshot>
-			<image>https://play-lh.googleusercontent.com/1zecL5SvayVkNoIHijeS_rm0Yr_XyHbp_dmSx70NXhjckvnezwvWkpINYBHAy4o8EaM=w2560-h1440</image>
+			<image type="source">https://play-lh.googleusercontent.com/1zecL5SvayVkNoIHijeS_rm0Yr_XyHbp_dmSx70NXhjckvnezwvWkpINYBHAy4o8EaM=w2560-h1440</image>
 		</screenshot>
 		<screenshot>
-			<image>https://play-lh.googleusercontent.com/e5ibvuWm442dYN2z_pAwDC3Ktc7XOY6FEI4N7BkFB7DqGi3Q-Vl46KqoJ_EFSrXRNw=w2560-h1440</image>
+			<image type="source">https://play-lh.googleusercontent.com/e5ibvuWm442dYN2z_pAwDC3Ktc7XOY6FEI4N7BkFB7DqGi3Q-Vl46KqoJ_EFSrXRNw=w2560-h1440</image>
 		</screenshot>
 		<screenshot>
-			<image>https://play-lh.googleusercontent.com/wD6xJK2nuzVyKUFODfQs2SEbUuSyEglojpp_FE6GeAuzdA_R44Dp6gTyN70KCwpRtD9M=w2560-h1440</image>
+			<image type="source">https://play-lh.googleusercontent.com/wD6xJK2nuzVyKUFODfQs2SEbUuSyEglojpp_FE6GeAuzdA_R44Dp6gTyN70KCwpRtD9M=w2560-h1440</image>
 		</screenshot>
 	</screenshots>
+	<releases>
+		<release version="1.4.2" date="2023-12-25" type="stable"/>
+		<release version="1.4.1" date="2023-12-12" type="stable"/>
+		<release version="1.4.0" date="2023-12-08" type="stable"/>
+		<release version="1.3.2" date="2023-09-15" type="stable"/>
+		<release version="1.3.1" date="2023-08-18" type="stable"/>
+		<release version="1.3.0" date="2023-08-04" type="stable"/>
+		<release version="1.2.1" date="2023-04-28" type="stable"/>
+		<release version="1.2.0" date="2023-04-14" type="stable"/>
+		<release version="1.1.1" date="2023-02-03" type="stable"/>
+		<release version="1.1.0" date="2022-12-23" type="stable"/>
+		<release version="1.0.0" date="2022-09-11" type="stable"/>
+		<release version="0.99" date="2016-11-01" type="stable"/>
+	</releases>
 	<url type="homepage">https://vcmi.eu/</url>
 	<url type="bugtracker">https://github.com/vcmi/vcmi/issues</url>
 	<url type="faq">https://vcmi.eu/faq/</url>
@@ -58,33 +96,15 @@
 	<url type="translate">https://github.com/vcmi/vcmi/blob/master/docs/modders/Translations.md</url>
 	<url type="contact">https://discord.gg/chBT42V</url>
 	<url type="vcs-browser">https://github.com/vcmi/vcmi</url>
-	<recommends>
-		<control>keyboard</control>
-		<control>pointing</control>
-		<control>touch</control>
-	</recommends>
 	<categories>
 		<category>Game</category>
 		<category>StrategyGame</category>
 	</categories>
-	<releases>
-		<release version="1.4.1" date="2023-12-12" />
-		<release version="1.4.0" date="2023-12-08" />
-		<release version="1.3.2" date="2023-09-15" />
-		<release version="1.3.1" date="2023-08-18" />
-		<release version="1.3.0" date="2023-08-04" />
-		<release version="1.2.1" date="2023-04-28" />
-		<release version="1.2.0" date="2023-04-14" />
-		<release version="1.1.1" date="2023-02-03" />
-		<release version="1.1.0" date="2022-12-23" />
-		<release version="1.0.0" date="2022-09-11" />
-		<release version="0.99" date="2016-11-01" />
-	</releases>
-	<keywords>
-		<keyword translate="no">heroes3</keyword>
-		<keyword translate="no">homm3</keyword>
-	</keywords>
-	<developer_name>VCMI Team</developer_name>
+	<recommends>
+		<control>pointing</control>
+		<control>keyboard</control>
+		<control>touch</control>
+	</recommends>
 	<content_rating type="oars-1.1">
 		<content_attribute id="violence-cartoon">moderate</content_attribute>
 		<content_attribute id="violence-fantasy">moderate</content_attribute>
@@ -98,4 +118,8 @@
 		<binary>vcmilauncher</binary>
 		<binary>vcmiserver</binary>
 	</provides>
+	<keywords>
+		<keyword>heroes3</keyword>
+		<keyword>homm3</keyword>
+	</keywords>
 </component>

+ 7 - 7
launcher/mainwindow_moc.cpp

@@ -136,14 +136,14 @@ void MainWindow::detectPreferredLanguage()
 		for (auto const & vcmiLang : Languages::getLanguageList())
 			if (vcmiLang.tagIETF == userLang.toStdString())
 				selectedLanguage = vcmiLang.identifier;
-	}
-
-	logGlobal->info("Selected language: %s", selectedLanguage);
 
-	if (!selectedLanguage.empty())
-	{
-		Settings node = settings.write["general"]["language"];
-		node->String() = selectedLanguage;
+		if (!selectedLanguage.empty())
+		{
+			logGlobal->info("Selected language: %s", selectedLanguage);
+			Settings node = settings.write["general"]["language"];
+			node->String() = selectedLanguage;
+			return;
+		}
 	}
 }
 

+ 9 - 1
launcher/modManager/cmodlistview_moc.cpp

@@ -591,7 +591,7 @@ void CModListView::downloadFile(QString file, QString url, QString description,
 			this, SLOT(downloadFinished(QStringList,QStringList,QStringList)));
 		
 		connect(manager.get(), SIGNAL(extractionProgress(qint64,qint64)),
-			this, SLOT(downloadProgress(qint64,qint64)));
+			this, SLOT(extractionProgress(qint64,qint64)));
 		
 		connect(modModel, &CModListModel::dataChanged, filterModel, &QAbstractItemModel::dataChanged);
 
@@ -613,6 +613,14 @@ void CModListView::downloadProgress(qint64 current, qint64 max)
 	ui->progressBar->setValue(current / (1024 * 1024));
 }
 
+void CModListView::extractionProgress(qint64 current, qint64 max)
+{
+	// display progress, in extracted files
+	ui->progressBar->setVisible(true);
+	ui->progressBar->setMaximum(max);
+	ui->progressBar->setValue(current);
+}
+
 void CModListView::downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors)
 {
 	QString title = tr("Download failed");

+ 1 - 0
launcher/modManager/cmodlistview_moc.h

@@ -98,6 +98,7 @@ private slots:
 	void dataChanged(const QModelIndex & topleft, const QModelIndex & bottomRight);
 	void modSelected(const QModelIndex & current, const QModelIndex & previous);
 	void downloadProgress(qint64 current, qint64 max);
+	void extractionProgress(qint64 current, qint64 max);
 	void downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors);
 	void modelReset();
 	void hideProgressBar();

+ 16 - 5
launcher/modManager/cmodmanager.cpp

@@ -24,7 +24,9 @@ namespace
 {
 QString detectModArchive(QString path, QString modName, std::vector<std::string> & filesToExtract)
 {
-	filesToExtract = ZipArchive::listFiles(qstringToPath(path));
+	ZipArchive archive(qstringToPath(path));
+
+	filesToExtract = archive.listFiles();
 
 	QString modDirName;
 
@@ -285,14 +287,23 @@ bool CModManager::doInstallMod(QString modname, QString archivePath)
 	if(!modDirName.size())
 		return addError(modname, "Mod archive is invalid or corrupted");
 	
-	auto futureExtract = std::async(std::launch::async, [&archivePath, &destDir, &filesToExtract]()
+	std::atomic<int> filesCounter = 0;
+
+	auto futureExtract = std::async(std::launch::async, [&archivePath, &destDir, &filesCounter, &filesToExtract]()
 	{
-		return ZipArchive::extract(qstringToPath(archivePath), qstringToPath(destDir), filesToExtract);
+		ZipArchive archive(qstringToPath(archivePath));
+		for (auto const & file : filesToExtract)
+		{
+			if (!archive.extract(qstringToPath(destDir), file))
+				return false;
+			++filesCounter;
+		}
+		return true;
 	});
 	
-	while(futureExtract.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready)
+	while(futureExtract.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready)
 	{
-		emit extractionProgress(0, 0);
+		emit extractionProgress(filesCounter, filesToExtract.size());
 		qApp->processEvents();
 	}
 	

+ 1207 - 0
launcher/translation/czech.ts

@@ -0,0 +1,1207 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="cs_CZ">
+<context>
+    <name>AboutProjectView</name>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="22"/>
+        <source>VCMI on Discord</source>
+        <translation>VCMI na Discordu</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="29"/>
+        <source>Have a question? Found a bug? Want to help? Join us!</source>
+        <translation>Máte otázku? Našli jste chybu? Chcete pomoct? Připojte se k nám!</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="36"/>
+        <source>VCMI on Github</source>
+        <translation>VCMI na GitHubu</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="55"/>
+        <source>Our Community</source>
+        <translation>Naše komunita</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="62"/>
+        <source>VCMI on Slack</source>
+        <translation>VCMI na Slacku</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="98"/>
+        <source>Build Information</source>
+        <translation>Informace o sestavení</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="115"/>
+        <source>User data directory</source>
+        <translation>Složka uživatelských dat</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="122"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="129"/>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="193"/>
+        <source>Open</source>
+        <translation>Otevřít</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="136"/>
+        <source>Check for updates</source>
+        <translation>Zkontrolovat aktualizace</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="156"/>
+        <source>Game version</source>
+        <translation>Verze hry</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="163"/>
+        <source>Log files directory</source>
+        <translation>Složka záznamů hry</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="176"/>
+        <source>Data Directories</source>
+        <translation>Složky dat</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="213"/>
+        <source>Game data directory</source>
+        <translation>Složka herních dat</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="220"/>
+        <source>Operating System</source>
+        <translation>Operační systém</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="273"/>
+        <source>Project homepage</source>
+        <translation>Domovská stránka projektu</translation>
+    </message>
+    <message>
+        <location filename="../aboutProject/aboutproject_moc.ui" line="286"/>
+        <source>Report a bug</source>
+        <translation>Nahlásit chybu</translation>
+    </message>
+</context>
+<context>
+    <name>CModListModel</name>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="42"/>
+        <source>Translation</source>
+        <translation>Překlad</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="43"/>
+        <source>Town</source>
+        <translation>Město</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="44"/>
+        <source>Test</source>
+        <translation>Zkouška</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="45"/>
+        <source>Templates</source>
+        <translation>Šablony</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="46"/>
+        <source>Spells</source>
+        <translation>Kouzla</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="47"/>
+        <source>Music</source>
+        <translation>Hudba</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="48"/>
+        <source>Maps</source>
+        <translation>Mapy</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="49"/>
+        <source>Sounds</source>
+        <translation>Zvuky</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="50"/>
+        <source>Skills</source>
+        <translation>Schopnosti</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="51"/>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="69"/>
+        <source>Other</source>
+        <translation>Ostatní</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="52"/>
+        <source>Objects</source>
+        <translation>Objekty</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="53"/>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="54"/>
+        <source>Mechanics</source>
+        <translation>Mechaniky</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="55"/>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="56"/>
+        <source>Interface</source>
+        <translation>Rozhraní</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="57"/>
+        <source>Heroes</source>
+        <translation>Hrdinové</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="58"/>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="59"/>
+        <source>Graphical</source>
+        <translation>Grafika</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="60"/>
+        <source>Expansion</source>
+        <translation>Rozšíření</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="61"/>
+        <source>Creatures</source>
+        <translation>Bojovníci</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="62"/>
+        <source>Compatibility</source>
+        <translation>Kompabilita</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="63"/>
+        <source>Artifacts</source>
+        <translation>Artefakty</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="64"/>
+        <source>AI</source>
+        <translation>AI</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="172"/>
+        <source>Name</source>
+        <translation>Název</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="175"/>
+        <source>Type</source>
+        <translation>Druh</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistmodel_moc.cpp" line="176"/>
+        <source>Version</source>
+        <translation>Verze</translation>
+    </message>
+</context>
+<context>
+    <name>CModListView</name>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="43"/>
+        <source>Filter</source>
+        <translation>Filtrovat</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="66"/>
+        <source>All mods</source>
+        <translation>Všechny modifikace</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="71"/>
+        <source>Downloadable</source>
+        <translation>Stahovatelné</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="76"/>
+        <source>Installed</source>
+        <translation>Nainstalované</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="81"/>
+        <source>Updatable</source>
+        <translation>Aktualizovatelné</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="86"/>
+        <source>Active</source>
+        <translation>Aktivní</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="91"/>
+        <source>Inactive</source>
+        <translation>Neaktivní</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="105"/>
+        <source>Download &amp;&amp; refresh repositories</source>
+        <translation>Stáhnout a aktualizovat repozitáře</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="163"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="316"/>
+        <source>Description</source>
+        <translation>Popis</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="209"/>
+        <source>Changelog</source>
+        <translation>Seznam změn</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="231"/>
+        <source>Screenshots</source>
+        <translation>Snímky obrazovky</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="389"/>
+        <source>Uninstall</source>
+        <translation>Odinstalovat</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="424"/>
+        <source>Enable</source>
+        <translation>Povolit</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="459"/>
+        <source>Disable</source>
+        <translation>Zakázat</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="494"/>
+        <source>Update</source>
+        <translation>Aktualizovat</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="529"/>
+        <source>Install</source>
+        <translation>Instalovat</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="327"/>
+        <source> %p% (%v KB out of %m KB)</source>
+        <translation> %p% (%v KB z %m KB)</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.ui" line="340"/>
+        <source>Abort</source>
+        <translation>Zrušit</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="248"/>
+        <source>Mod name</source>
+        <translation>Název modifikace</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="249"/>
+        <source>Installed version</source>
+        <translation>Nainstalovaná verze</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="250"/>
+        <source>Latest version</source>
+        <translation>Nejnovější verze</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="253"/>
+        <source>Size</source>
+        <translation>Velikost</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="255"/>
+        <source>Download size</source>
+        <translation>Velikost ke stažení</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="257"/>
+        <source>Authors</source>
+        <translation>Autoři</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="260"/>
+        <source>License</source>
+        <translation>Licence</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="263"/>
+        <source>Contact</source>
+        <translation>Kontakt</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="272"/>
+        <source>Compatibility</source>
+        <translation>Kompabilita</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="274"/>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="282"/>
+        <source>Required VCMI version</source>
+        <translation>Vyžadovaná verze VCMI</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="280"/>
+        <source>Supported VCMI version</source>
+        <translation>Podporovaná verze VCMI</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="285"/>
+        <source>Supported VCMI versions</source>
+        <translation>Podporované verze VCMI</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="312"/>
+        <source>Languages</source>
+        <translation>Jazyky</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="314"/>
+        <source>Required mods</source>
+        <translation>Vyžadované modifikace VCMI</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="315"/>
+        <source>Conflicting mods</source>
+        <translation>Modifikace v kolizi</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="320"/>
+        <source>This mod can not be installed or enabled because the following dependencies are not present</source>
+        <translation>Tato modifikace nemůže být nainstalována nebo povolena, protože následující závislosti nejsou přítomny</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="321"/>
+        <source>This mod can not be enabled because the following mods are incompatible with it</source>
+        <translation>Tato modifikace nemůže být povolena, protože následující modifikace s ní nejsou kompatibilní</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="322"/>
+        <source>This mod cannot be disabled because it is required by the following mods</source>
+        <translation>Tato modifikace nemůže být zakázána, protože je vyžadována následujícími modifikacemi</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="323"/>
+        <source>This mod cannot be uninstalled or updated because it is required by the following mods</source>
+        <translation>Tato modifikace nemůže být odinstalována nebo aktualizována, protože je vyžadována následujícími modifikacemi</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="324"/>
+        <source>This is a submod and it cannot be installed or uninstalled separately from its parent mod</source>
+        <translation>Toto je podmodifikace, která nemůže být nainstalována nebo odinstalována bez její rodičovské modifikace</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="339"/>
+        <source>Notes</source>
+        <translation>Poznámky</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="599"/>
+        <source>Downloading %s%. %p% (%v MB out of %m MB) finished</source>
+        <translation>Stahování %s%. %p% (%v MB z %m MB) dokončeno</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="618"/>
+        <source>Download failed</source>
+        <translation>Stahování selhalo</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="619"/>
+        <source>Unable to download all files.
+
+Encountered errors:
+
+</source>
+        <translation>Nelze stáhnout všechny soubory.
+
+Vyskytly se chyby:
+
+</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="620"/>
+        <source>
+
+Install successfully downloaded?</source>
+        <translation>
+
+Nainstalovat úspěšně stažené?</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="759"/>
+        <source>Installing mod %1</source>
+        <translation>Instalování modifikace %1</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="811"/>
+        <source>Operation failed</source>
+        <translation>Operace selhala</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="812"/>
+        <source>Encountered errors:
+</source>
+        <translation>Vyskytly se chyby:
+</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="847"/>
+        <source>Screenshot %1</source>
+        <translation>Snímek obrazovky %1</translation>
+    </message>
+    <message>
+        <location filename="../modManager/cmodlistview_moc.cpp" line="243"/>
+        <source>Mod is incompatible</source>
+        <translation>Modifikace není kompatibilní</translation>
+    </message>
+</context>
+<context>
+    <name>CSettingsView</name>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="276"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="580"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="649"/>
+        <source>Off</source>
+        <translation>Vypnuto</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="78"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="195"/>
+        <source>Artificial Intelligence</source>
+        <translation>Umělá inteligence</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="83"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="484"/>
+        <source>Mod Repositories</source>
+        <translation>Repozitáře modifikací</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="144"/>
+        <source>Interface Scaling</source>
+        <translation>Škálování rozhraní</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="624"/>
+        <source>Neutral AI in battles</source>
+        <translation>Neutrální AI v bitvách</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="289"/>
+        <source>Enemy AI in battles</source>
+        <translation>Nepřátelská AI v bitvách</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="239"/>
+        <source>Additional repository</source>
+        <translation>Další repozitáře</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="158"/>
+        <source>Adventure Map Allies</source>
+        <translation>Spojenci na mapě světa</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="638"/>
+        <source>Adventure Map Enemies</source>
+        <translation>Nepřátelé na mapě světa</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="353"/>
+        <source>Windowed</source>
+        <translation>V okně</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="358"/>
+        <source>Borderless fullscreen</source>
+        <translation>Celá obrazovka bez okrajů</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="363"/>
+        <source>Exclusive fullscreen</source>
+        <translation>Exkluzivní celá obrazovka</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="229"/>
+        <source>Autosave limit (0 = off)</source>
+        <translation>Limit aut. uložení (0=vypnuto)</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="436"/>
+        <source>Friendly AI in battles</source>
+        <translation>Přátelské AI v bitvách</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="429"/>
+        <source>Framerate Limit</source>
+        <translation>Omezení snímků za sekundu</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="151"/>
+        <source>Autosave prefix</source>
+        <translation>Předpona aut. uložení</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="202"/>
+        <source>empty = map name prefix</source>
+        <translation>prázná = předpona - název mapy</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="593"/>
+        <source>Refresh now</source>
+        <translation>Obnovit nyní</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="405"/>
+        <source>Default repository</source>
+        <translation>Výchozí repozitář</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="281"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="585"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="654"/>
+        <source>On</source>
+        <translation>Zapnuto</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="525"/>
+        <source>Cursor</source>
+        <translation>Kurzor</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="412"/>
+        <source>Heroes III Data Language</source>
+        <translation>Jazyk dat Heroes III</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="340"/>
+        <source>Select display mode for game
+
+Windowed - game will run inside a window that covers part of your screen
+
+Borderless Windowed Mode - game will run in a window that covers entirely of your screen, using same resolution as your screen.
+
+Fullscreen Exclusive Mode - game will cover entirety of your screen and will use selected resolution.</source>
+        <translation>Vyberte režim zobrazení pro hru
+
+V okně - hra bude běžet v okně zakrývajícím část vaší obrazovky
+
+Celá obrazovka bez okrajů-  hra poběží v okně, které zakryje vaši celou obrazovku se stejným rozlišením.
+
+Exkluzivní celá obrazovka - hra zakryje vaši celou obrazovku a použije vybrané rozlišení.</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="631"/>
+        <source>Reserved screen area</source>
+        <translation>Vyhrazená část obrazovky</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="533"/>
+        <source>Hardware</source>
+        <translation>Hardware</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="538"/>
+        <source>Software</source>
+        <translation>Software</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="118"/>
+        <source>Heroes III Translation</source>
+        <translation>Překlad Heroes III</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="617"/>
+        <source>Check on startup</source>
+        <translation>Zkontrolovat při zapnutí</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="137"/>
+        <source>Fullscreen</source>
+        <translation>Celá obrazovka</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="68"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="130"/>
+        <source>General</source>
+        <translation>Všeobecné</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="443"/>
+        <source>VCMI Language</source>
+        <translation>Jazyk VCMI</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="498"/>
+        <source>Resolution</source>
+        <translation>Rozlišení</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="518"/>
+        <source>Autosave</source>
+        <translation>Automatické uložení</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="662"/>
+        <source>VSync</source>
+        <translation>VSync</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="374"/>
+        <source>Display index</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="549"/>
+        <source>Network port</source>
+        <translation>Síťový port</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="73"/>
+        <location filename="../settingsView/csettingsview_moc.ui" line="170"/>
+        <source>Video</source>
+        <translation>Zobrazení</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.ui" line="265"/>
+        <source>Show intro</source>
+        <translation>Zobrazit intro</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="409"/>
+        <source>Active</source>
+        <translation>Aktivní</translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="414"/>
+        <source>Disabled</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="415"/>
+        <source>Enable</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="420"/>
+        <source>Not Installed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="421"/>
+        <source>Install</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>Chat</name>
+    <message>
+        <location filename="../lobby/chat_moc.ui" line="14"/>
+        <source>Form</source>
+        <translation>Formulář</translation>
+    </message>
+    <message>
+        <location filename="../lobby/chat_moc.ui" line="40"/>
+        <source>Users in lobby</source>
+        <translation>Uživatelé v předsíni</translation>
+    </message>
+    <message>
+        <location filename="../lobby/chat_moc.ui" line="50"/>
+        <source>Global chat</source>
+        <translation>Obecná konverzace</translation>
+    </message>
+    <message>
+        <location filename="../lobby/chat_moc.ui" line="104"/>
+        <source>type you message</source>
+        <translation>zadejte vaši zprávu</translation>
+    </message>
+    <message>
+        <location filename="../lobby/chat_moc.ui" line="111"/>
+        <source>send</source>
+        <translation>poslat</translation>
+    </message>
+</context>
+<context>
+    <name>FirstLaunchView</name>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="28"/>
+        <source>Language</source>
+        <translation>Jazyk</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="53"/>
+        <source>Heroes III Data</source>
+        <translation>Data Heroes III</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="78"/>
+        <source>Mods Preset</source>
+        <translation>Předvybrané modifikace</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="127"/>
+        <source>Select your language</source>
+        <translation>Vyberte váš jazyk</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="177"/>
+        <source>Have a question? Found a bug? Want to help? Join us!</source>
+        <translation>Máte otázku? Našli jste chybu? Chcete pomoct? Připojte se k nám!</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="186"/>
+        <source>Thank you for installing VCMI!
+
+Before you can start playing, there are a few more steps that need to be completed.
+
+Please keep in mind that in order to use VCMI you must own the original data files for Heroes® of Might and Magic® III: Complete or The Shadow of Death.
+
+Heroes® of Might and Magic® III HD is currently not supported!</source>
+        <translation>Děkujeme za instalaci VCMI!
+
+Před začátkem hraní musíte ještě dokončit pár kroků.
+
+Prosíme, mějte na paměti, že abyste mohli hrát VCMI, musíte vlastnit originální datové soubory Heroes® of Might and Magic® III: Complete nebo The Shadow of Death.
+
+Heroes® of Might and Magic® III HD není v současnosti podporovaný!</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="257"/>
+        <source>Locate Heroes III data files</source>
+        <translation>Najít soubory dat Heroes III</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="346"/>
+        <source>If you don&apos;t have a copy of Heroes III installed, you can use our automatic installation tool &apos;vcmibuilder&apos;, which only requires the GoG.com Heroes III installer. Please visit our wiki for detailed instructions.</source>
+        <translation>Pokud nemáte Heroes III nainstalované, můžete použít náš automatický instalační nástroj &apos;vcmibuilder&apos;, který vyžaduje pouze GOG instalátor Heroes III. Prosíme, navštivte naši wiki pro podrobné instrukce.</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="362"/>
+        <source>To run VCMI, Heroes III data files need to be present in one of the specified locations. Please copy the Heroes III data to one of these directories.</source>
+        <translation>Pro běh VCMI, datové soubory Heroes III musí být přítomny v jednom z určených umístění. Prosíme, zkopírujte data Heroes do jedné z těchto složek.</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="397"/>
+        <source>Alternatively, you can provide the directory where Heroes III data is installed and VCMI will copy the existing data automatically.</source>
+        <translation>Nebo můžete poskytnout složku s instalací Heroes III a VCMI zkopíruje existující data automaticky.</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="426"/>
+        <source>Your Heroes III data files have been successfully found.</source>
+        <translation>Vaše soubory dat Heroes III byly úspěšně nalezeny.</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="466"/>
+        <source>The automatic detection of the Heroes III language has failed. Please select the language of your Heroes III manually</source>
+        <translation>Automatické rozpoznání jazyka Heroes III selhalo. Prosíme, vyberte jazyk vašich Heroes III ručně</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="702"/>
+        <source>Interface Improvements</source>
+        <translation>Vylepšení rozhraní</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="740"/>
+        <source>Install a translation of Heroes III in your preferred language</source>
+        <translation>Instalovat překlad Heroes III vašeho upřednostněného jazyka</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="756"/>
+        <source>Optionally, you can install additional mods either now, or at any point later, using the VCMI Launcher</source>
+        <translation>Nyní můžete volitelně nainstalovat další modifikace, nebo též kdykoliv potom pomocí spouštěče VCMI</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="772"/>
+        <source>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</source>
+        <translation>Instalovat modifikaci, která poskytuje různá vylepšení rozhraní, například lepší rozhraní pro náhodné mapy a volitelné akce v bitvách</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="788"/>
+        <source>Install compatible version of &quot;Horn of the Abyss&quot;, a fan-made Heroes III expansion ported by the VCMI team</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="804"/>
+        <source>Install compatible version of &quot;In The Wake of Gods&quot;, a fan-made Heroes III expansion</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="851"/>
+        <source>Finish</source>
+        <translation>Dokončit</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="156"/>
+        <source>VCMI on Github</source>
+        <translation>VCMI na GitHubu</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="163"/>
+        <source>VCMI on Slack</source>
+        <translation>VCMI na Slacku</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="170"/>
+        <source>VCMI on Discord</source>
+        <translation>VCMI na Discordu</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="220"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="535"/>
+        <source>Next</source>
+        <translation>Další</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="288"/>
+        <source>Open help in browser</source>
+        <translation>Otevřít nápovědu v prohlížeči</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="301"/>
+        <source>Search again</source>
+        <translation>Hledat znovu</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="384"/>
+        <source>Heroes III data files</source>
+        <translation>Soubory dat Heroes III</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="413"/>
+        <source>Copy existing data</source>
+        <translation>Kopírovat existující data</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="456"/>
+        <source>Your Heroes III language has been successfully detected.</source>
+        <translation>Váš jazyk Heroes III byl úspěšně zjištěn.</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="485"/>
+        <source>Heroes III language</source>
+        <translation>Jazyk Heroes III</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="528"/>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="844"/>
+        <source>Back</source>
+        <translation>Zpět</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="566"/>
+        <source>Install VCMI Mod Preset</source>
+        <translation>Instalovat předvybrané modifiakce VCMI</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="616"/>
+        <source>Horn of the Abyss</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="638"/>
+        <source>Heroes III Translation</source>
+        <translation>Překlady Heroes III</translation>
+    </message>
+    <message>
+        <location filename="../firstLaunch/firstlaunch_moc.ui" line="724"/>
+        <source>In The Wake of Gods</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>ImageViewer</name>
+    <message>
+        <location filename="../modManager/imageviewer_moc.ui" line="20"/>
+        <source>Image Viewer</source>
+        <translation>Prohlížeč obrázků</translation>
+    </message>
+</context>
+<context>
+    <name>Language</name>
+    <message>
+        <location filename="../languages.cpp" line="23"/>
+        <source>Czech</source>
+        <translation>Čeština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="24"/>
+        <source>Chinese</source>
+        <translation>Čínština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="25"/>
+        <source>English</source>
+        <translation>Angličtina</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="26"/>
+        <source>Finnish</source>
+        <translation>Finština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="27"/>
+        <source>French</source>
+        <translation>Francouzština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="28"/>
+        <source>German</source>
+        <translation>Němčina</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="29"/>
+        <source>Hungarian</source>
+        <translation>Maďarština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="30"/>
+        <source>Italian</source>
+        <translation>Italština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="31"/>
+        <source>Korean</source>
+        <translation>Korejština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="32"/>
+        <source>Polish</source>
+        <translation>Polština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="33"/>
+        <source>Portuguese</source>
+        <translation>Portugalština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="34"/>
+        <source>Russian</source>
+        <translation>Ruština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="35"/>
+        <source>Spanish</source>
+        <translation>Španělština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="36"/>
+        <source>Swedish</source>
+        <translation>Švédština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="37"/>
+        <source>Turkish</source>
+        <translation>Turečtina</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="38"/>
+        <source>Ukrainian</source>
+        <translation>Ukrajinština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="39"/>
+        <source>Vietnamese</source>
+        <translation>Vietnamština</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="40"/>
+        <source>Other (East European)</source>
+        <translation>Ostatní (východní Evropa)</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="41"/>
+        <source>Other (Cyrillic Script)</source>
+        <translation>Ostatní (azbuka)</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="42"/>
+        <source>Other (West European)</source>
+        <translation>Ostatní (západní Evropa)</translation>
+    </message>
+    <message>
+        <location filename="../languages.cpp" line="64"/>
+        <source>Auto (%1)</source>
+        <translation>Automaticky (%1)</translation>
+    </message>
+</context>
+<context>
+    <name>Lobby</name>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="42"/>
+        <location filename="../lobby/lobby_moc.cpp" line="402"/>
+        <source>Connect</source>
+        <translation>Připojit</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="29"/>
+        <source>Username</source>
+        <translation>Uživatelské jméno</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="59"/>
+        <source>Server</source>
+        <translation>Server</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="173"/>
+        <source>Session</source>
+        <translation>Relace</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="178"/>
+        <source>Players</source>
+        <translation>Hráči</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="268"/>
+        <source>Resolve</source>
+        <translation>Vyřešit</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="280"/>
+        <source>New game</source>
+        <translation>Nová hra</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="287"/>
+        <source>Load game</source>
+        <translation>Načíst hru</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="128"/>
+        <source>New room</source>
+        <translation>Nová místnost</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="138"/>
+        <source>Join room</source>
+        <translation>Připojit se do místnosti</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="261"/>
+        <source>Ready</source>
+        <translation>Připraven</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="244"/>
+        <source>Mods mismatch</source>
+        <translation>Nesoulad modifikací</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="237"/>
+        <source>Leave</source>
+        <translation>Odejít</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="210"/>
+        <source>Kick player</source>
+        <translation>Vyhodit hráče</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.ui" line="230"/>
+        <source>Players in the room</source>
+        <translation>Hráči v místnosti</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.cpp" line="369"/>
+        <source>Disconnect</source>
+        <translation>Odpojit</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobby_moc.cpp" line="462"/>
+        <source>No issues detected</source>
+        <translation>Bez problémů</translation>
+    </message>
+</context>
+<context>
+    <name>LobbyRoomRequest</name>
+    <message>
+        <location filename="../lobby/lobbyroomrequest_moc.ui" line="17"/>
+        <source>Room settings</source>
+        <translation>Nastavení místnosti</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobbyroomrequest_moc.ui" line="32"/>
+        <source>Room name</source>
+        <translation>Název místnosti</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobbyroomrequest_moc.ui" line="42"/>
+        <source>Maximum players</source>
+        <translation>Maximum hráčů</translation>
+    </message>
+    <message>
+        <location filename="../lobby/lobbyroomrequest_moc.ui" line="97"/>
+        <source>Password (optional)</source>
+        <translation>Heslo (volitelné)</translation>
+    </message>
+</context>
+<context>
+    <name>MainWindow</name>
+    <message>
+        <location filename="../mainwindow_moc.ui" line="20"/>
+        <source>VCMI Launcher</source>
+        <translation>Spouštěč VCMI</translation>
+    </message>
+    <message>
+        <location filename="../mainwindow_moc.ui" line="107"/>
+        <source>Settings</source>
+        <translation>Nastavení</translation>
+    </message>
+    <message>
+        <location filename="../mainwindow_moc.ui" line="207"/>
+        <source>Help</source>
+        <translation>Nápověda</translation>
+    </message>
+    <message>
+        <location filename="../mainwindow_moc.ui" line="276"/>
+        <source>Map Editor</source>
+        <translation>Editor map</translation>
+    </message>
+    <message>
+        <location filename="../mainwindow_moc.ui" line="329"/>
+        <source>Start game</source>
+        <translation>Spustit hru</translation>
+    </message>
+    <message>
+        <location filename="../mainwindow_moc.ui" line="157"/>
+        <source>Lobby</source>
+        <translation>Předsíň</translation>
+    </message>
+    <message>
+        <location filename="../mainwindow_moc.ui" line="57"/>
+        <source>Mods</source>
+        <translation>Modifikace</translation>
+    </message>
+</context>
+<context>
+    <name>UpdateDialog</name>
+    <message>
+        <location filename="../updatedialog_moc.ui" line="71"/>
+        <source>You have the latest version</source>
+        <translation>Máte nejnovější verzi</translation>
+    </message>
+    <message>
+        <location filename="../updatedialog_moc.ui" line="94"/>
+        <source>Close</source>
+        <translation>Zavřít</translation>
+    </message>
+    <message>
+        <location filename="../updatedialog_moc.ui" line="101"/>
+        <source>Check for updates on startup</source>
+        <translation>Zkontrolovat aktualizace při startu</translation>
+    </message>
+</context>
+</TS>

+ 20 - 6
lib/BasicTypes.cpp

@@ -93,6 +93,16 @@ int AFactionMember::getPrimSkillLevel(PrimarySkill id) const
 
 int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const
 {
+	int32_t maxGoodMorale = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE).size();
+	int32_t maxBadMorale = - (int32_t) VLC->settings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE).size();
+
+	if(getBonusBearer()->hasBonusOfType(BonusType::MAX_MORALE))
+	{
+		if(bonusList && !bonusList->empty())
+			bonusList = std::make_shared<const BonusList>();
+		return maxGoodMorale;
+	}
+
 	static const auto unaffectedByMoraleSelector = Selector::type()(BonusType::NON_LIVING).Or(Selector::type()(BonusType::UNDEAD))
 													.Or(Selector::type()(BonusType::SIEGE_WEAPON)).Or(Selector::type()(BonusType::NO_MORALE));
 
@@ -109,14 +119,21 @@ int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const
 	static const std::string cachingStrMor = "type_MORALE";
 	bonusList = getBonusBearer()->getBonuses(moraleSelector, cachingStrMor);
 
-	int32_t maxGoodMorale = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE).size();
-	int32_t maxBadMorale = - (int32_t) VLC->settings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE).size();
-
 	return std::clamp(bonusList->totalValue(), maxBadMorale, maxGoodMorale);
 }
 
 int AFactionMember::luckValAndBonusList(TConstBonusListPtr & bonusList) const
 {
+	int32_t maxGoodLuck = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_LUCK_DICE).size();
+	int32_t maxBadLuck = - (int32_t) VLC->settings()->getVector(EGameSettings::COMBAT_BAD_LUCK_DICE).size();
+
+	if(getBonusBearer()->hasBonusOfType(BonusType::MAX_LUCK))
+	{
+		if(bonusList && !bonusList->empty())
+			bonusList = std::make_shared<const BonusList>();
+		return maxGoodLuck;
+	}
+
 	if(getBonusBearer()->hasBonusOfType(BonusType::NO_LUCK))
 	{
 		if(bonusList && !bonusList->empty())
@@ -128,9 +145,6 @@ int AFactionMember::luckValAndBonusList(TConstBonusListPtr & bonusList) const
 	static const std::string cachingStrLuck = "type_LUCK";
 	bonusList = getBonusBearer()->getBonuses(luckSelector, cachingStrLuck);
 
-	int32_t maxGoodLuck = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_LUCK_DICE).size();
-	int32_t maxBadLuck = - (int32_t) VLC->settings()->getVector(EGameSettings::COMBAT_BAD_LUCK_DICE).size();
-
 	return std::clamp(bonusList->totalValue(), maxBadLuck, maxGoodLuck);
 }
 

+ 5 - 3
lib/CArtifactInstance.cpp

@@ -64,10 +64,7 @@ SpellID CScrollArtifactInstance::getScrollSpellID() const
 	auto artInst = static_cast<const CArtifactInstance*>(this);
 	const auto bonus = artInst->getBonusLocalFirst(Selector::type()(BonusType::SPELL));
 	if(!bonus)
-	{
-		logMod->warn("Warning: %s doesn't bear any spell!", artInst->nodeName());
 		return SpellID::NONE;
-	}
 	return bonus->subtype.as<SpellID>();
 }
 
@@ -165,6 +162,11 @@ bool CArtifactInstance::isCombined() const
 	return artType->isCombined();
 }
 
+bool CArtifactInstance::isScroll() const
+{
+	return artType->isScroll();
+}
+
 void CArtifactInstance::putAt(CArtifactSet & set, const ArtifactPosition slot)
 {
 	auto placementMap = set.putArtifact(slot, this);

+ 1 - 0
lib/CArtifactInstance.h

@@ -87,6 +87,7 @@ public:
 	bool canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot = ArtifactPosition::FIRST_AVAILABLE,
 		bool assumeDestRemoved = false) const;
 	bool isCombined() const;
+	bool isScroll() const;
 	void putAt(CArtifactSet & set, const ArtifactPosition slot);
 	void removeFrom(CArtifactSet & set, const ArtifactPosition slot);
 	void move(CArtifactSet & srcSet, const ArtifactPosition srcSlot, CArtifactSet & dstSet, const ArtifactPosition dstSlot);

+ 5 - 0
lib/CGameInfoCallback.cpp

@@ -721,6 +721,11 @@ bool CGameInfoCallback::isPlayerMakingTurn(PlayerColor player) const
 	return gs->actingPlayers.count(player);
 }
 
+CGameInfoCallback::CGameInfoCallback():
+	gs(nullptr)
+{
+}
+
 CGameInfoCallback::CGameInfoCallback(CGameState * GS):
 	gs(GS)
 {

+ 1 - 1
lib/CGameInfoCallback.h

@@ -134,7 +134,7 @@ class DLL_LINKAGE CGameInfoCallback : public IGameInfoCallback
 protected:
 	CGameState * gs;//todo: replace with protected const getter, only actual Server and Client objects should hold game state
 
-	CGameInfoCallback() = default;
+	CGameInfoCallback();
 	CGameInfoCallback(CGameState * GS);
 	bool hasAccess(std::optional<PlayerColor> playerId) const;
 

+ 2 - 3
lib/GameSettings.cpp

@@ -118,11 +118,10 @@ void GameSettings::load(const JsonNode & input)
 
 const JsonNode & GameSettings::getValue(EGameSettings option) const
 {
-	assert(option < EGameSettings::OPTIONS_COUNT);
 	auto index = static_cast<size_t>(option);
 
-	assert(!gameSettings[index].isNull());
-	return gameSettings[index];
+	assert(!gameSettings.at(index).isNull());
+	return gameSettings.at(index);
 }
 
 VCMI_LIB_NAMESPACE_END

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است