浏览代码

Merge pull request #3265 from vcmi/master

Merge master -> develop
Ivan Savenko 1 年之前
父节点
当前提交
ef61456681
共有 100 个文件被更改,包括 1842 次插入422 次删除
  1. 14 1
      .github/workflows/github.yml
  2. 1 1
      AI/BattleAI/BattleAI.cpp
  3. 2 1
      AI/Nullkiller/Analyzers/HeroManager.cpp
  4. 2 7
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp
  5. 1 1
      CI/msvc/before_install.sh
  6. 5 0
      CMakeLists.txt
  7. 1 2
      CMakePresets.json
  8. 10 1
      ChangeLog.md
  9. 二进制
      Mods/vcmi/Sprites/lobby/checkboxBlueOff.png
  10. 二进制
      Mods/vcmi/Sprites/lobby/checkboxBlueOn.png
  11. 8 0
      Mods/vcmi/Sprites/lobby/dropdown.json
  12. 二进制
      Mods/vcmi/Sprites/lobby/dropdownNormal.png
  13. 二进制
      Mods/vcmi/Sprites/lobby/dropdownPressed.png
  14. 2 2
      Mods/vcmi/config/vcmi/chinese.json
  15. 37 5
      Mods/vcmi/config/vcmi/english.json
  16. 93 1
      Mods/vcmi/config/vcmi/german.json
  17. 105 0
      Mods/vcmi/config/vcmi/polish.json
  18. 2 2
      Mods/vcmi/config/vcmi/russian.json
  19. 44 4
      Mods/vcmi/config/vcmi/ukrainian.json
  20. 2 2
      Mods/vcmi/config/vcmi/vietnamese.json
  21. 11 5
      client/CServerHandler.cpp
  22. 2 1
      client/HeroMovementController.cpp
  23. 1 1
      client/NetPacksClient.cpp
  24. 4 1
      client/adventureMap/AdventureMapInterface.cpp
  25. 2 2
      client/adventureMap/TurnTimerWidget.cpp
  26. 1 1
      client/battle/BattleActionsController.cpp
  27. 1 1
      client/battle/BattleStacksController.cpp
  28. 3 3
      client/gui/InterfaceObjectConfigurable.cpp
  29. 10 2
      client/lobby/OptionsTab.cpp
  30. 112 19
      client/lobby/OptionsTabBase.cpp
  31. 10 0
      client/lobby/OptionsTabBase.h
  32. 2 2
      client/mainmenu/CMainMenu.cpp
  33. 1 0
      client/widgets/CGarrisonInt.cpp
  34. 27 23
      client/widgets/ComboBox.cpp
  35. 6 3
      client/widgets/ComboBox.h
  36. 2 4
      client/widgets/Images.cpp
  37. 0 4
      client/widgets/Images.h
  38. 2 0
      client/widgets/TextControls.cpp
  39. 2 2
      client/windows/CCastleInterface.cpp
  40. 4 0
      client/windows/CMessage.cpp
  41. 20 5
      client/windows/CSpellWindow.cpp
  42. 17 1
      client/windows/CTradeWindow.cpp
  43. 1 1
      client/windows/GUIClasses.cpp
  44. 3 1
      client/windows/InfoWindows.cpp
  45. 9 0
      client/windows/settings/GeneralOptionsTab.cpp
  46. 5 5
      config/heroes/stronghold.json
  47. 21 1
      config/objects/generic.json
  48. 7 0
      config/objects/moddables.json
  49. 1 2
      config/objects/rewardableOnceVisitable.json
  50. 10 9
      config/objects/rewardablePickable.json
  51. 6 1
      config/schemas/settings.json
  52. 129 0
      config/widgets/advancedOptionsTab.json
  53. 35 43
      config/widgets/playerOptionsTab.json
  54. 0 1
      config/widgets/settings/battleOptionsTab.json
  55. 8 0
      config/widgets/settings/generalOptionsTab.json
  56. 520 0
      config/widgets/turnOptionsDropdownLibrary.json
  57. 92 12
      config/widgets/turnOptionsTab.json
  58. 1 1
      debian/changelog
  59. 1 3
      docs/Readme.md
  60. 1 1
      launcher/eu.vcmi.VCMI.metainfo.xml
  61. 5 5
      launcher/translation/chinese.ts
  62. 5 5
      launcher/translation/english.ts
  63. 5 5
      launcher/translation/french.ts
  64. 24 17
      launcher/translation/german.ts
  65. 47 40
      launcher/translation/polish.ts
  66. 5 5
      launcher/translation/russian.ts
  67. 5 5
      launcher/translation/spanish.ts
  68. 5 5
      launcher/translation/ukrainian.ts
  69. 5 5
      launcher/translation/vietnamese.ts
  70. 0 3
      lib/CArtHandler.cpp
  71. 1 19
      lib/CPlayerState.cpp
  72. 5 2
      lib/MetaString.cpp
  73. 7 0
      lib/StartInfo.h
  74. 1 1
      lib/TurnTimerInfo.cpp
  75. 17 2
      lib/TurnTimerInfo.h
  76. 1 7
      lib/gameState/CGameState.cpp
  77. 6 0
      lib/mapObjectConstructors/AObjectTypeHandler.cpp
  78. 3 0
      lib/mapObjectConstructors/AObjectTypeHandler.h
  79. 0 7
      lib/mapObjects/CGHeroInstance.cpp
  80. 11 7
      lib/mapObjects/CGObjectInstance.cpp
  81. 5 0
      lib/mapObjects/CGObjectInstance.h
  82. 5 0
      lib/mapObjects/CGTownInstance.cpp
  83. 1 0
      lib/mapObjects/CGTownInstance.h
  84. 3 1
      lib/mapObjects/CQuest.h
  85. 16 4
      lib/mapObjects/IObjectInterface.cpp
  86. 6 3
      lib/mapObjects/MiscObjects.cpp
  87. 2 7
      lib/mapping/CMap.cpp
  88. 1 1
      lib/mapping/MapFeaturesH3M.cpp
  89. 27 21
      lib/mapping/MapFormatH3M.cpp
  90. 20 5
      lib/mapping/MapFormatJson.cpp
  91. 4 1
      lib/mapping/MapReaderH3M.cpp
  92. 31 2
      lib/pathfinder/CPathfinder.cpp
  93. 1 0
      lib/pathfinder/CPathfinder.h
  94. 1 1
      lib/rmg/CMapGenOptions.cpp
  95. 76 22
      lib/rmg/RmgObject.cpp
  96. 8 0
      lib/rmg/RmgObject.h
  97. 2 2
      lib/rmg/modificators/ObjectDistributor.cpp
  98. 19 3
      lib/rmg/modificators/ObjectManager.cpp
  99. 1 1
      lib/rmg/modificators/RoadPlacer.cpp
  100. 36 17
      lib/rmg/modificators/TreasurePlacer.cpp

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

@@ -84,6 +84,7 @@ jobs:
             os: macos-12
             test: 0
             pack: 1
+            pack_type: Release
             extension: dmg
             preset: macos-conan-ninja-release
             conan_profile: macos-intel
@@ -93,6 +94,7 @@ jobs:
             os: macos-12
             test: 0
             pack: 1
+            pack_type: Release
             extension: dmg
             preset: macos-arm-conan-ninja-release
             conan_profile: macos-arm
@@ -102,6 +104,7 @@ jobs:
             os: macos-12
             test: 0
             pack: 1
+            pack_type: Release
             extension: ipa
             preset: ios-release-conan-ccache
             conan_profile: ios-arm64
@@ -110,12 +113,14 @@ jobs:
             os: windows-latest
             test: 0
             pack: 1
+            pack_type: RelWithDebInfo
             extension: exe
             preset: windows-msvc-release-ccache
           - platform: mingw-ubuntu
             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
@@ -246,7 +251,7 @@ jobs:
       run: |
         cd '${{github.workspace}}/out/build/${{matrix.preset}}'
         CPACK_PATH=`which -a cpack | grep -m1 -v -i chocolatey`
-        "$CPACK_PATH" -C ${{env.BUILD_TYPE}} ${{ matrix.cpack_args }}
+        "$CPACK_PATH" -C ${{matrix.pack_type}} ${{ matrix.cpack_args }}
         test -f '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' \
           && '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' '${{github.workspace}}' "$(ls '${{ env.VCMI_PACKAGE_FILE_NAME }}'.*)"
         rm -rf _CPack_Packages
@@ -279,6 +284,14 @@ jobs:
         name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
         path: |
           ${{ env.ANDROID_APK_PATH }}
+          
+    - name: Symbols
+      if: ${{ matrix.platform == 'msvc' }}
+      uses: actions/upload-artifact@v3
+      with:
+        name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - symbols
+        path: |
+            ${{github.workspace}}/**/*.pdb
 
     - name: Android JNI ${{matrix.platform}}
       if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }}

+ 1 - 1
AI/BattleAI/BattleAI.cpp

@@ -145,7 +145,7 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
 
 		result = evaluator.selectStackAction(stack);
 
-		if(!skipCastUntilNextBattle && evaluator.canCastSpell())
+		if(autobattlePreferences.enableSpellsUsage && !skipCastUntilNextBattle && evaluator.canCastSpell())
 		{
 			auto spelCasted = evaluator.attemptCastingSpell(stack);
 

+ 2 - 1
AI/Nullkiller/Analyzers/HeroManager.cpp

@@ -187,7 +187,8 @@ bool HeroManager::heroCapReached() const
 	int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned);
 
 	return heroCount >= ALLOWED_ROAMING_HEROES
-		|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
+		|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)
+		|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP);
 }
 
 float HeroManager::getMagicStrength(const CGHeroInstance * hero) const

+ 2 - 7
AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp

@@ -91,12 +91,12 @@ namespace AIPathfinding
 
 		for(const CGHeroInstance * hero : nodeStorage->getAllHeroes())
 		{
-			if(hero->canCastThisSpell(waterWalk.toSpell()))
+			if(hero->canCastThisSpell(waterWalk.toSpell()) && hero->mana >= hero->getSpellCost(waterWalk.toSpell()))
 			{
 				waterWalkingActions[hero] = std::make_shared<WaterWalkingAction>(hero);
 			}
 
-			if(hero->canCastThisSpell(airWalk.toSpell()))
+			if(hero->canCastThisSpell(airWalk.toSpell()) && hero->mana >= hero->getSpellCost(airWalk.toSpell()))
 			{
 				airWalkingActions[hero] = std::make_shared<AirWalkingAction>(hero);
 			}
@@ -179,11 +179,6 @@ namespace AIPathfinding
 	{
 		bool result = false;
 
-		if(!specialAction->canAct(nodeStorage->getAINode(source.node)))
-		{
-			return false;
-		}
-
 		nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
 			{
 				auto castNodeOptional = nodeStorage->getOrCreateNode(

+ 1 - 1
CI/msvc/before_install.sh

@@ -1,5 +1,5 @@
 curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z" \
-	"https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.6/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z"
+	"https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.7/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z"
 7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z"
 
 #rm -r -f vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug

+ 5 - 0
CMakeLists.txt

@@ -715,6 +715,11 @@ endif(WIN32)
 #       Packaging section             #
 #######################################
 
+if(MSVC)
+	SET(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ${BIN_DIR})
+	Include(InstallRequiredSystemLibraries)
+endif()
+
 set(CPACK_PACKAGE_VERSION_MAJOR ${VCMI_VERSION_MAJOR})
 set(CPACK_PACKAGE_VERSION_MINOR ${VCMI_VERSION_MINOR})
 set(CPACK_PACKAGE_VERSION_PATCH ${VCMI_VERSION_PATCH})

+ 1 - 2
CMakePresets.json

@@ -327,8 +327,7 @@
         {
             "name": "windows-msvc-release",
             "configurePreset": "windows-msvc-release",
-            "inherits": "default-release",
-            "configuration": "Release"
+            "inherits": "default-release"
         },
         {
             "name": "windows-msvc-release-ccache",

+ 10 - 1
ChangeLog.md

@@ -102,6 +102,8 @@
 * Spell scrolls in hero inventory now show icon of contained spell
 * Fixed incorrect hero morale tooltip after visiting adventure map objects
 * Fixed incorrect information for skills in hero exchange window
+* Confirmation button will now be disabled on automatic server connect dialog
+* Attempting to recruit creature in town with no free slots in garrisons will now correctly show error message
 
 ### Main Menu
 * Implemented window for quick selection of starting hero, town and bonus
@@ -147,13 +149,20 @@
 * Creature that attacks while standing in moat will now correctly receive moat damage
 * Player resources are now limited to 1 000 000 000 to prevent overflow
 * It is no longer possible to escape from town without fort
+* Pathfinder will no longer make U-turns when moving onto visitable objects while flying
+* Pathfinder will no longer make paths that go over teleporters without actually using them
+* Game will now correctly update guard status of tiles that are guarded by multiple wandering monsters
+* Moving onto Garrisons and Border Guards entrance tiles that are guarded by wandering monsters will now correctly trigger battle
+* It is no longer possible to build second boat in shipyard when shipyard should be blocked by boat with hero
+* Gundula is now Offense specialist and not Sorcery, as in H3
 
 ### Random Maps Generator
 * Increased tolerance for placement of Subterranean Gates
 * Game will now select random object template out of available options instead of picking first one
 * It is no longer possible to create map with a single team
+* Game will no longer route roads through non-removable treasure objects, such as Corpse
+* Fixed placement of treasure piles with non-removable objects, such as Corpse
 * Fixed interface no displaying correct random map settings in some cases
-* Fixed game failing to generate random map if number of AI players is set to non-zero
 * Fixed misleading error "no info for player X found"
 * Fixed bug leading to AI players defeated on day one.
 

二进制
Mods/vcmi/Sprites/lobby/checkboxBlueOff.png


二进制
Mods/vcmi/Sprites/lobby/checkboxBlueOn.png


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

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

二进制
Mods/vcmi/Sprites/lobby/dropdownNormal.png


二进制
Mods/vcmi/Sprites/lobby/dropdownPressed.png


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

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

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

@@ -111,7 +111,9 @@
 	"vcmi.systemOptions.hapticFeedbackButton.hover"  : "Haptic feedback",
 	"vcmi.systemOptions.hapticFeedbackButton.help"   : "{Haptic feedback}\n\nToggle the haptic feedback on touch inputs.",
 	"vcmi.systemOptions.enableUiEnhancementsButton.hover"  : "Interface Enhancements",
-	"vcmi.systemOptions.enableUiEnhancementsButton.help"   : "{Interface Enhancements}\n\nToggle various quality of life interface improvements. Such as a larger spell book, backpack, etc. Disable to have a more classic experience.",
+	"vcmi.systemOptions.enableUiEnhancementsButton.help"   : "{Interface Enhancements}\n\nToggle various quality of life interface improvements. Such as a backpack button etc. Disable to have a more classic experience.",
+	"vcmi.systemOptions.enableLargeSpellbookButton.hover"  : "Large Spell Book",
+	"vcmi.systemOptions.enableLargeSpellbookButton.help"   : "{Large Spell Book}\n\nEnables larger spell book that fits more spells per page. Spell book page change animation does not work with this setting enabled.",
 
 	"vcmi.adventureOptions.infoBarPick.hover" : "Show Messages in Info Panel",
 	"vcmi.adventureOptions.infoBarPick.help" : "{Show Messages in Info Panel}\n\nWhenever possible, game messages from visiting map objects will be shown in the info panel, instead of popping up in a separate window.",
@@ -234,17 +236,22 @@
 
 	"vcmi.optionsTab.turnOptions.hover" : "Turn Options",
 	"vcmi.optionsTab.turnOptions.help" : "Select turn timer and simultaneous turns options",
+	"vcmi.optionsTab.selectPreset" : "Preset",
 
 	"vcmi.optionsTab.chessFieldBase.hover" : "Base timer",
 	"vcmi.optionsTab.chessFieldTurn.hover" : "Turn timer",
 	"vcmi.optionsTab.chessFieldBattle.hover" : "Battle timer",
-	"vcmi.optionsTab.chessFieldCreature.hover" : "Unit timer",
+	"vcmi.optionsTab.chessFieldUnit.hover" : "Unit timer",
 	"vcmi.optionsTab.chessFieldBase.help" : "Used when {Turn Timer} reaches 0. Set once at game start. On reaching zero, ends current turn. Any ongoing combat with end with a loss.",
-	"vcmi.optionsTab.chessFieldTurn.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Leftover added to {Base Timer} at turn's end.",
+	"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Leftover added to {Base Timer} at turn's end.",
+	"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Any unspent time is lost",
 	"vcmi.optionsTab.chessFieldBattle.help" : "Used in battles with AI or in pvp combat when {Unit Timer} runs out. Reset at start of each combat.",
-	"vcmi.optionsTab.chessFieldCreature.help" : "Used when selecting unit action in pvp combat. Reset at start of each unit's turn.",
+	"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Used when selecting unit action in pvp combat. Leftover added to {Battle Timer} at end of unit turn.",
+	"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Used when selecting unit action in pvp combat. Reset at start of each unit's turn. Any unspent time is lost",
 
-	"vcmi.optionsTab.simturns" : "Simultaneous turns",
+	"vcmi.optionsTab.accumulate" : "Accumulate",
+
+	"vcmi.optionsTab.simturnsTitle" : "Simultaneous turns",
 	"vcmi.optionsTab.simturnsMin.hover" : "At least for",
 	"vcmi.optionsTab.simturnsMax.hover" : "At most for",
 	"vcmi.optionsTab.simturnsAI.hover" : "(Experimental) Simultaneous AI Turns",
@@ -252,6 +259,31 @@
 	"vcmi.optionsTab.simturnsMax.help" : "Play simultaneously for specified number of days or until contact with another player",
 	"vcmi.optionsTab.simturnsAI.help" : "{Simultaneous AI Turns}\nExperimental option. Allows AI players to act at the same time as human player when simultaneous turns are enabled.",
 	
+	"vcmi.optionsTab.turnTime.select"     : "Select turn timer preset",
+	"vcmi.optionsTab.turnTime.unlimited"  : "Unlimited turn time",
+	"vcmi.optionsTab.turnTime.classic.1"  : "Classic timer: 1 minute",
+	"vcmi.optionsTab.turnTime.classic.2"  : "Classic timer: 2 minutes",
+	"vcmi.optionsTab.turnTime.classic.5"  : "Classic timer: 5 minutes",
+	"vcmi.optionsTab.turnTime.classic.10" : "Classic timer: 10 minutes",
+	"vcmi.optionsTab.turnTime.classic.20" : "Classic timer: 20 minutes",
+	"vcmi.optionsTab.turnTime.classic.30" : "Classic timer: 30 minutes",
+	"vcmi.optionsTab.turnTime.chess.20"   : "Chess: 20:00 + 10:00 + 02:00 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.16"   : "Chess: 16:00 + 08:00 + 01:30 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.8"    : "Chess: 08:00 + 04:00 + 01:00 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.4"    : "Chess: 04:00 + 02:00 + 00:30 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.2"    : "Chess: 02:00 + 01:00 + 00:15 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.1"    : "Chess: 01:00 + 01:00 + 00:00 + 00:00",
+
+	"vcmi.optionsTab.simturns.select"         : "Select simultaneous turns preset",
+	"vcmi.optionsTab.simturns.none"           : "No simultaneous turns",
+	"vcmi.optionsTab.simturns.tillContactMax" : "Simturns: Until contact",
+	"vcmi.optionsTab.simturns.tillContact1"   : "Simturns: 1 week, break on contact",
+	"vcmi.optionsTab.simturns.tillContact2"   : "Simturns: 2 weeks, break on contact",
+	"vcmi.optionsTab.simturns.tillContact4"   : "Simturns: 1 month, break on contact",
+	"vcmi.optionsTab.simturns.blocked1"       : "Simturns: 1 week, contacts blocked",
+	"vcmi.optionsTab.simturns.blocked2"       : "Simturns: 2 weeks, contacts blocked",
+	"vcmi.optionsTab.simturns.blocked4"       : "Simturns: 1 month, contacts blocked",
+	
 	// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
 	// Using this information, VCMI will automatically select correct plural form for every possible amount
 	"vcmi.optionsTab.simturns.days.0" : " %d days",

+ 93 - 1
Mods/vcmi/config/vcmi/german.json

@@ -36,10 +36,23 @@
 	"vcmi.heroOverview.spells" : "Zaubersprüche",
 	
 	"vcmi.radialWheel.mergeSameUnit" : "Gleiche Kreaturen zusammenführen",
+	"vcmi.radialWheel.fillSingleUnit" : "Füllen mit einzelnen Kreaturen",
 	"vcmi.radialWheel.splitSingleUnit" : "Wegtrennen einzelner Kreaturen",
 	"vcmi.radialWheel.splitUnitEqually" : "Gleichmäßiges trennen der Kreaturen",
 	"vcmi.radialWheel.moveUnit" : "Verschieben der Kreatur in andere Armee",
 	"vcmi.radialWheel.splitUnit" : "Aufsplitten der Kreatur in anderen Slot",
+	
+	"vcmi.radialWheel.heroGetArmy" : "Armee von anderem Helden erhalten",
+	"vcmi.radialWheel.heroSwapArmy" : "Tausche Armee mit anderem Helden",
+	"vcmi.radialWheel.heroExchange" : "Öffne Tausch-Menü",
+	"vcmi.radialWheel.heroGetArtifacts" : "Artefakte von anderen Helden erhalten",
+	"vcmi.radialWheel.heroSwapArtifacts" : "Tausche Artefakte mit anderen Helden",
+	"vcmi.radialWheel.heroDismiss" : "Held entlassen",
+
+	"vcmi.radialWheel.moveTop" : "Ganz nach oben bewegen",
+	"vcmi.radialWheel.moveUp" : "Nach oben bewegen",
+	"vcmi.radialWheel.moveDown" : "Nach unten bewegen",
+	"vcmi.radialWheel.moveBottom" : "Ganz nach unten bewegen",
 
 	"vcmi.mainMenu.serverConnecting" : "Verbinde...",
 	"vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:",
@@ -58,7 +71,11 @@
 
 	"vcmi.server.errors.existingProcess" : "Es läuft ein weiterer vcmiserver-Prozess, bitte beendet diesen zuerst",
 	"vcmi.server.errors.modsToEnable"    : "{Erforderliche Mods um das Spiel zu laden}",
+	"vcmi.server.errors.modsToDisable"   : "{Folgende Mods müssen deaktiviert werden}",
 	"vcmi.server.confirmReconnect"       : "Mit der letzten Sitzung verbinden?",
+	"vcmi.server.errors.modNoDependency" : "Mod {'%s'} konnte nicht geladen werden!\n Sie hängt von Mod {'%s'} ab, die nicht aktiv ist!\n",
+	"vcmi.server.errors.modConflict" : "Mod {'%s'} konnte nicht geladen werden!\n Konflikte mit aktiver Mod {'%s'}!\n",
+	"vcmi.server.errors.unknownEntity" : "Spielstand konnte nicht geladen werden! Unbekannte Entität '%s' im gespeicherten Spiel gefunden! Der Spielstand ist möglicherweise nicht mit der aktuell installierten Version der Mods kompatibel!",
 
 	"vcmi.settingsMainWindow.generalTab.hover" : "Allgemein",
 	"vcmi.settingsMainWindow.generalTab.help"     : "Wechselt zur Registerkarte Allgemeine Optionen, die Einstellungen zum allgemeinen Verhalten des Spielclients enthält.",
@@ -94,7 +111,9 @@
 	"vcmi.systemOptions.hapticFeedbackButton.hover"  : "Haptisches Feedback",
 	"vcmi.systemOptions.hapticFeedbackButton.help"   : "{Haptisches Feedback}\n\nHaptisches Feedback bei Touch-Eingaben.",
 	"vcmi.systemOptions.enableUiEnhancementsButton.hover"  : "Interface Verbesserungen",
-	"vcmi.systemOptions.enableUiEnhancementsButton.help"   : "{Interface Verbesserungen}\n\nSchaltet verschiedene Interface Verbesserungen um. Wie z.B. ein größeres Zauberbuch, Rucksack, etc. Deaktivieren, um ein klassischeres Erlebnis zu haben.",
+	"vcmi.systemOptions.enableUiEnhancementsButton.help"   : "{Interface Verbesserungen}\n\nSchaltet verschiedene Interface Verbesserungen um. Wie z.B. ein Rucksack-Button, etc. Deaktivieren, um ein klassischeres Erlebnis zu haben.",
+	"vcmi.systemOptions.enableLargeSpellbookButton.hover"  : "Großes Zauberbuch",
+	"vcmi.systemOptions.enableLargeSpellbookButton.help"   : "{Großes Zauberbuch}\n\nErmöglicht ein größeres Zauberbuch, in das mehr Zaubersprüche pro Seite passen. Die Animation des Seitenwechsels im Zauberbuch funktioniert nicht, wenn diese Einstellung aktiviert ist.",
 
 	"vcmi.adventureOptions.infoBarPick.hover" : "Meldungen im Infobereich anzeigen",
 	"vcmi.adventureOptions.infoBarPick.help" : "{Meldungen im Infobereich anzeigen}\n\nWann immer möglich, werden Spielnachrichten von besuchten Kartenobjekten in der Infoleiste angezeigt, anstatt als Popup-Fenster zu erscheinen",
@@ -140,6 +159,9 @@
 	"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Statistikfenster für Helden anzeigen}\n\nDauerhaftes Einschalten des Statistikfenster für Helden, das die primären Werte und Zauberpunkte anzeigt.",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Intro-Musik überspringen",
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Intro-Musik überspringen}\n\n Überspringe die kurze Musik, die zu Beginn eines jeden Kampfes gespielt wird, bevor die Action beginnt. Kann auch durch Drücken der ESC-Taste übersprungen werden.",
+	
+	"vcmi.adventureMap.revisitObject.hover" : "Objekt erneut besuchen",
+	"vcmi.adventureMap.revisitObject.help" : "{Objekt erneut besuchen}\n\nSteht ein Held gerade auf einem Kartenobjekt, kann er den Ort erneut aufsuchen.",
 
 	"vcmi.battleWindow.pressKeyToSkipIntro" : "Beliebige Taste drücken, um das Kampf-Intro zu überspringen",
 	"vcmi.battleWindow.damageEstimation.melee" : "Angriff auf %CREATURE (%DAMAGE).",
@@ -155,6 +177,15 @@
 
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Kampfergebnis übernehmen",
 
+	"vcmi.tutorialWindow.title" : "Touchscreen Einführung",
+	"vcmi.tutorialWindow.decription.RightClick" : "Berührt und haltet das Element, auf das mit der rechten Maustaste geklickt werden soll. Berührt den freien Bereich, um zu schließen.",
+	"vcmi.tutorialWindow.decription.MapPanning" : "Berührt und zieht mit einem Finger, um die Karte zu verschieben.",
+	"vcmi.tutorialWindow.decription.MapZooming" : "Berührt mit zwei Fingern, um den Kartenzoom zu ändern.",
+	"vcmi.tutorialWindow.decription.RadialWheel" : "Durch Streichen öffnet sich das Radialrad für verschiedene Aktionen, wie z.B. Kreaturen-/Heldenverwaltung und Stadtreihenfolge.",
+	"vcmi.tutorialWindow.decription.BattleDirection" : "Um aus einer bestimmten Richtung anzugreifen, wischt in die Richtung, aus der der Angriff erfolgen soll.",
+	"vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Die Geste für die Angriffsrichtung kann abgebrochen werden, wenn der Finger weit genug entfernt ist.",
+	"vcmi.tutorialWindow.decription.AbortSpell" : "Berühren und halten, um einen Zauber abzubrechen.",
+
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Verfügbare Kreaturen anzeigen",
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Verfügbare Kreaturen anzeigen}\n\n Zeigt in der Stadtübersicht (linke untere Ecke) die zum Kauf verfügbaren Kreaturen anstelle ihres Wachstums an.",
 	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Wöchentl. Wachstum der Kreaturen anz.",
@@ -182,6 +213,8 @@
 
 	"vcmi.heroWindow.openCommander.hover" : "Öffne Kommandanten-Fenster",
 	"vcmi.heroWindow.openCommander.help"  : "Zeige Informationen über Kommandanten dieses Helden",
+	"vcmi.heroWindow.openBackpack.hover" : "Artefakt-Rucksack-Fenster öffnen",
+	"vcmi.heroWindow.openBackpack.help"  : "Öffnet ein Fenster, das die Verwaltung des Artefakt-Rucksacks erleichtert",
 
 	"vcmi.commanderWindow.artifactMessage" : "Möchtet Ihr diesen Artefakt dem Helden zurückgeben?",
 
@@ -201,6 +234,65 @@
 	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Team-Zuordnungen",
 	"vcmi.randomMapTab.widgets.roadTypesLabel"       : "Straßentypen",
 
+	"vcmi.optionsTab.turnOptions.hover" : "Spielzug-Optionen",
+	"vcmi.optionsTab.turnOptions.help" : "Optionen zu Spielzug-Timer und simultanen Zügen",
+
+	"vcmi.optionsTab.chessFieldBase.hover" : "Basis-Timer",
+	"vcmi.optionsTab.chessFieldTurn.hover" : "Spielzug-Timer",
+	"vcmi.optionsTab.chessFieldBattle.hover" : "Kampf-Timer",
+	"vcmi.optionsTab.chessFieldUnit.hover" : "Einheiten-Timer",
+	"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Wird außerhalb des Kampfes verwendet oder wenn der {Kampf-Timer} abgelaufen ist. Wird jede Runde zurückgesetzt. Reste werden am Ende der Runde zum {Basis-Timer} hinzugefügt.",
+	"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Wird außerhalb des Kampfes verwendet oder wenn der {Kampf-Timer} abgelaufen ist. Wird jede Runde zurückgesetzt. Jede nicht verbrauchte Zeit ist verloren",
+	"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Wird bei der Auswahl der Einheitenaktion im PvP-Kampf verwendet. Der Rest wird am Ende des Zuges der Einheit zum {Kampf-Timer} hinzugefügt.",
+	"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Wird bei der Auswahl der Einheitenaktion im PvP-Kampf verwendet. Wird zu Beginn des Zuges jeder Einheit zurückgesetzt. Jede nicht verbrauchte Zeit ist verloren",
+
+	"vcmi.optionsTab.accumulate" : "Akkumulieren",
+
+	"vcmi.optionsTab.turnTime.select"     : "Spielzug-Timer-Voreinst. wählen",
+	"vcmi.optionsTab.turnTime.unlimited"  : "Unbegrenzter Spielzug-Timer",
+	"vcmi.optionsTab.turnTime.classic.1"  : "Klassischer Timer: 1 Minute",
+	"vcmi.optionsTab.turnTime.classic.2"  : "Klassischer Timer: 2 Minuten",
+	"vcmi.optionsTab.turnTime.classic.5"  : "Klassischer Timer: 5 Minuten",
+	"vcmi.optionsTab.turnTime.classic.10" : "Klassischer Timer: 10 Minuten",
+	"vcmi.optionsTab.turnTime.classic.20" : "Klassischer Timer: 20 Minuten",
+	"vcmi.optionsTab.turnTime.classic.30" : "Klassischer Timer: 30 Minuten",
+	"vcmi.optionsTab.turnTime.chess.20"   : "Schach: 20:00 10:00 02:00 00:00",
+	"vcmi.optionsTab.turnTime.chess.16"   : "Schach: 16:00 08:00 01:30 00:00",
+	"vcmi.optionsTab.turnTime.chess.8"    : "Schach: 08:00 04:00 01:00 00:00",
+	"vcmi.optionsTab.turnTime.chess.4"    : "Schach: 04:00 02:00 00:30 00:00",
+	"vcmi.optionsTab.turnTime.chess.2"    : "Schach: 02:00 01:00 00:15 00:00",
+	"vcmi.optionsTab.turnTime.chess.1"    : "Schach: 01:00 01:00 00:00 00:00",
+
+	"vcmi.optionsTab.simturns.select"         : "Voreinst. für simultane Züge wählen",
+	"vcmi.optionsTab.simturns.none"           : "Keine simultanen Züge",
+	"vcmi.optionsTab.simturns.tillContactMax" : "Simzüge: Bis zum Kontakt",
+	"vcmi.optionsTab.simturns.tillContact1"   : "Simzüge: 1 Woche, Stop bei Kontakt",
+	"vcmi.optionsTab.simturns.tillContact2"   : "Simzüge: 2 Wochen, Stop bei Kontakt",
+	"vcmi.optionsTab.simturns.tillContact4"   : "Simzüge: 1 Monat, Stop bei Kontakt",
+	"vcmi.optionsTab.simturns.blocked1"       : "Simzüge: 1 Woche, Kontakte block.",
+	"vcmi.optionsTab.simturns.blocked2"       : "Simzüge: 2 Wochen, Kontakte block.",
+	"vcmi.optionsTab.simturns.blocked4"       : "Simzüge: 1 Monat, Kontakte block.",
+
+	"vcmi.optionsTab.simturnsTitle" : "Simultane Züge",
+	"vcmi.optionsTab.simturnsMin.hover" : "Zumindest für",
+	"vcmi.optionsTab.simturnsMax.hover" : "Höchstens für",
+	"vcmi.optionsTab.simturnsAI.hover" : "(Experimentell) Simultane KI Züge",
+	"vcmi.optionsTab.simturnsMin.help" : "Spielt gleichzeitig für eine bestimmte Anzahl von Tagen. Die Kontakte zwischen den Spielern sind während dieser Zeit blockiert",
+	"vcmi.optionsTab.simturnsMax.help" : "Spielt gleichzeitig für eine bestimmte Anzahl von Tagen oder bis zum Kontakt mit einem anderen Spieler",
+	"vcmi.optionsTab.simturnsAI.help" : "{Simultane KI Züge}\nExperimentelle Option. Ermöglicht es den KI-Spielern, gleichzeitig mit dem menschlichen Spieler zu agieren, wenn simultane Spielzüge aktiviert sind.",
+	
+	// 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 Tage",
+	"vcmi.optionsTab.simturns.days.1" : "%d Tag",
+	"vcmi.optionsTab.simturns.days.2" : "%d Tage",
+	"vcmi.optionsTab.simturns.weeks.0" : "%d Wochen",
+	"vcmi.optionsTab.simturns.weeks.1" : "%d Woche",
+	"vcmi.optionsTab.simturns.weeks.2" : "%d Wochen",
+	"vcmi.optionsTab.simturns.months.0" : "%d Monate",
+	"vcmi.optionsTab.simturns.months.1" : "%d Monat",
+	"vcmi.optionsTab.simturns.months.2" : "%d Monate",
+
 	// Custom victory conditions for H3 campaigns and HotA maps
 	"vcmi.map.victoryCondition.daysPassed.toOthers" : "Der Feind hat es geschafft, bis zum heutigen Tag zu überleben. Der Sieg gehört ihm!",
 	"vcmi.map.victoryCondition.daysPassed.toSelf" : "Herzlichen Glückwunsch! Ihr habt es geschafft, zu überleben. Der Sieg ist euer!",

+ 105 - 0
Mods/vcmi/config/vcmi/polish.json

@@ -30,14 +30,33 @@
 	"vcmi.capitalColors.6" : "Jasnoniebieski",
 	"vcmi.capitalColors.7" : "Różowy",
 
+	"vcmi.heroOverview.startingArmy" : "Jednostki startowe",
+	"vcmi.heroOverview.warMachine" : "Machiny wojenne",
+	"vcmi.heroOverview.secondarySkills" : "Umiejętności drugorzędne",
+	"vcmi.heroOverview.spells" : "Zaklęcia",
+
 	"vcmi.radialWheel.mergeSameUnit" : "Złącz takie same stworzenia",
+	"vcmi.radialWheel.fillSingleUnit" : "Wypełnij pojedynczymi stworzeniami",
 	"vcmi.radialWheel.splitSingleUnit" : "Wydziel pojedyncze stworzenie",
 	"vcmi.radialWheel.splitUnitEqually" : "Podziel stworzenia równo",
 	"vcmi.radialWheel.moveUnit" : "Przenieś stworzenia do innej armii",
 	"vcmi.radialWheel.splitUnit" : "Podziel jednostkę do wybranego miejsca",
 
+	"vcmi.radialWheel.heroGetArmy" : "Weź armię z innego bohatera",
+	"vcmi.radialWheel.heroSwapArmy" : "Zamień armię z innym bohaterem",
+	"vcmi.radialWheel.heroExchange" : "Rozpocznij wymianę między bohaterami",
+	"vcmi.radialWheel.heroGetArtifacts" : "Weź artefakty z innego bohatera",
+	"vcmi.radialWheel.heroSwapArtifacts" : "Zamień artefakty z innym bohaterem",
+	"vcmi.radialWheel.heroDismiss" : "Dymisja bohatera",
+
+	"vcmi.radialWheel.moveTop" : "Przenieś na początek",
+	"vcmi.radialWheel.moveUp" : "Przenieś w górę",
+	"vcmi.radialWheel.moveDown" : "Przenieś w dół",
+	"vcmi.radialWheel.moveBottom" : "Przenieś na spód",
+
 	"vcmi.mainMenu.serverConnecting" : "Łączenie...",
 	"vcmi.mainMenu.serverAddressEnter" : "Wprowadź adres:",
+	"vcmi.mainMenu.serverConnectionFailed" : "Połączenie nie powiodło się",
 	"vcmi.mainMenu.serverClosing" : "Zamykanie...",
 	"vcmi.mainMenu.hostTCP" : "Hostuj grę TCP/IP",
 	"vcmi.mainMenu.joinTCP" : "Dołącz do gry TCP/IP",
@@ -45,10 +64,18 @@
 
 	"vcmi.lobby.filepath" : "Nazwa pliku",
 	"vcmi.lobby.creationDate" : "Data utworzenia",
+	"vcmi.lobby.scenarioName" : "Nazwa scenariusza",
+	"vcmi.lobby.mapPreview" : "Podgląd mapy",
+	"vcmi.lobby.noPreview" : "brak podglądu",
+	"vcmi.lobby.noUnderground" : "brak podziemi",
 
 	"vcmi.server.errors.existingProcess" : "Inny proces 'vcmiserver' został już uruchomiony, zakończ go nim przejdziesz dalej",
 	"vcmi.server.errors.modsToEnable"    : "{Następujące mody są wymagane do wczytania gry}",
+	"vcmi.server.errors.modsToDisable"   : "{Następujące mody muszą zostać wyłączone}",
 	"vcmi.server.confirmReconnect"       : "Połączyć ponownie z ostatnią sesją?",
+	"vcmi.server.errors.modNoDependency" : "Nie udało się wczytać moda {'%s'}!\n Jest on zależny od moda {'%s'} który nie jest aktywny!\n",
+	"vcmi.server.errors.modConflict" : "Nie udało się wczytać moda {'%s'}!\n Konflikty z aktywnym modem {'%s'}!\n",
+	"vcmi.server.errors.unknownEntity" : "Nie udało się wczytać zapisu! Nieznany element '%s' znaleziony w pliku zapisu! Zapis może nie być zgodny z aktualnie zainstalowaną wersją modów!",
 
 	"vcmi.settingsMainWindow.generalTab.hover"   : "Ogólne",
 	"vcmi.settingsMainWindow.generalTab.help"    : "Przełącza do zakładki opcji ogólnych, która zawiera ustawienia związane z ogólnym działaniem gry",
@@ -83,6 +110,10 @@
 	"vcmi.systemOptions.framerateButton.help"   : "{Pokaż FPS}\n\n Przełącza widoczność licznika klatek na sekundę (FPS) w rogu okna gry.",
 	"vcmi.systemOptions.hapticFeedbackButton.hover"  : "Wibracje urządzenia",
 	"vcmi.systemOptions.hapticFeedbackButton.help"   : "{Wibracje urządzenia}\n\nWłącz wibracje na urządzeniu dotykowym",
+	"vcmi.systemOptions.enableUiEnhancementsButton.hover"  : "Ulepszenia interfejsu",
+	"vcmi.systemOptions.enableUiEnhancementsButton.help"   : "{Ulepszenia interfejsu}\n\nWłącza różne ulepszenia interfejsu poprawiające wygodę rozgrywki. Takie jak przycisk sakwy bohatera itp. Wyłącz jeśli szukasz bardziej klasycznej wersji gry.",
+	"vcmi.systemOptions.enableLargeSpellbookButton.hover"  : "Duża księga zaklęć",
+	"vcmi.systemOptions.enableLargeSpellbookButton.help"   : "{Duża księga zaklęć}\n\nWłącza dużą księgę czarów, która mieści więcej zaklęć na stronę. Animacja zmiany strony nie działa gdy ta opcja jest włączona.",
 
 	"vcmi.adventureOptions.infoBarPick.hover" : "Pokaż komunikaty w panelu informacyjnym",
 	"vcmi.adventureOptions.infoBarPick.help" : "{Pokaż komunikaty w panelu informacyjnym}\n\nGdy to możliwe, wiadomości z odwiedzania obiektów będą pokazywane w panelu informacyjnym zamiast w osobnym okienku.",
@@ -129,6 +160,9 @@
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Pomiń czekanie startowe",
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Pomiń czekanie startowe}\n\n Pomija konieczność czekania podczas muzyki startowej, która jest odtwarzana na początku każdej bitwy przed rozpoczęciem akcji.",
 
+	"vcmi.adventureMap.revisitObject.hover" : "Odwiedź obiekt ponownie",
+	"vcmi.adventureMap.revisitObject.help" : "{Odwiedź obiekt ponownie}\n\nJeżeli bohater aktualnie stoi na polu odwiedzającym obiekt za pomocą tego przycisku może go odwiedzić ponownie.",
+
 	"vcmi.battleWindow.pressKeyToSkipIntro" : "Naciśnij dowolny klawisz by rozpocząć bitwę natychmiastowo",
 	"vcmi.battleWindow.damageEstimation.melee" : "Atakuj %CREATURE (%DAMAGE).",
 	"vcmi.battleWindow.damageEstimation.meleeKills" : "Atakuj %CREATURE (%DAMAGE, %KILLS).",
@@ -143,6 +177,15 @@
 
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Zatwierdź wynik bitwy",
 
+	"vcmi.tutorialWindow.title" : "Samouczek ekranu dotykowego",
+	"vcmi.tutorialWindow.decription.RightClick" : "Dotknij i przytrzymaj palec na elemencie na którym chcesz wykonać akcję prawego przycisku myszy. Dotknij wolny obszar by zamknąć.",
+	"vcmi.tutorialWindow.decription.MapPanning" : "Dotknij i przeciągnij jednym palcem by przesunąć mapę.",
+	"vcmi.tutorialWindow.decription.MapZooming" : "Za pomocą gestu szczypania / rozwierania dwóch palców możesz zmieniać powiększenie mapy.",
+	"vcmi.tutorialWindow.decription.RadialWheel" : "Przeciąganie palcem otwiera kołowe menu dla różnych akcji takich jak zarządzanie stworzeniami/bohaterami i sortowanie miast.",
+	"vcmi.tutorialWindow.decription.BattleDirection" : "By zaatakować z określonego kierunku przeciągnij palcem w stronę kierunku z którego chcesz zaatakować.",
+	"vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Atak z wyborem kierunku może zostać anulowany jeśli palec znajdzie się wystarczająco daleko.",
+	"vcmi.tutorialWindow.decription.AbortSpell" : "Naciśnij i przytrzymaj by anulować rzucenie zaklęcia.",
+
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Pokaż dostępne stworzenia",
 	"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Pokaż dostępne stworzenia}\n\n Pokazuje dostępne stworzenia zamiast tygodniowego przyrostu w podsumowaniu miasta (lewy dolny róg).",
 	"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Pokaż tygodniowy przyrost stworzeń",
@@ -191,6 +234,68 @@
 	"vcmi.randomMapTab.widgets.teamAlignmentsLabel"  : "Sojusze",
 	"vcmi.randomMapTab.widgets.roadTypesLabel"       : "Typy dróg",
 
+	"vcmi.optionsTab.turnOptions.hover" : "Ustawienia tur",
+	"vcmi.optionsTab.turnOptions.help" : "Ustaw limity czasu (timery) oraz tury równoczesne",
+	"vcmi.optionsTab.selectPreset" : "Szablonowe ustawienie",
+
+	"vcmi.optionsTab.chessFieldBase.hover" : "Timer startowy",
+	"vcmi.optionsTab.chessFieldTurn.hover" : "Timer tury",
+	"vcmi.optionsTab.chessFieldBattle.hover" : "Timer bitwy",
+	"vcmi.optionsTab.chessFieldUnit.hover" : "Timer jednostki",
+	"vcmi.optionsTab.chessFieldBase.help" : "Używany gdy {Timer tury} osiągnie 0. Ustawiany raz przy starcie gry. Gdy osiągnie 0 tura się kończy, a trwająca bitwa zostanie przegrana.",
+	"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Używany poza bitwą lub gdy {Timer bitwy} się wyczerpie. Odnawia się co turę. Nadwyżka czasu dodaje się do {Timera startowego} pod koniec tury.",
+	"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Używany poza bitwą lub gdy {Timer bitwy} się wyczerpie. Odnawia się co turę. Niewykorzystany czas zostaje utracony.",
+	"vcmi.optionsTab.chessFieldBattle.help" : "Używany w bitwach z graczem AI lub gdy {Timer jednostki} się wyczerpie w bitwie pomiędzy graczami. Odnawia się przy starcie każdej bitwy.",
+	"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Używany podczas oczekiwania na podjęcie akcji jednostką w bitwie pomiędzy ludźmi. Nadwyżka czasu dodaje się do {Timera bitwy}",
+	"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Używany podczas oczekiwania na podjęcie akcji jednostką w bitwie pomiędzy ludźmi. Resetuje się przy rozpoczęciu akcji jednostką.",
+
+	"vcmi.optionsTab.accumulate" : "Akumuluj",
+
+	"vcmi.optionsTab.simturnsTitle" : "Tury równoczesne / symultaniczne",
+	"vcmi.optionsTab.simturnsMin.hover" : "Co najmniej przez",
+	"vcmi.optionsTab.simturnsMax.hover" : "Maks. przez",
+	"vcmi.optionsTab.simturnsAI.hover" : "(Eksperymentalne) Równoczesne tury graczy AI",
+	"vcmi.optionsTab.simturnsMin.help" : "Graj równocześnie przez określoną liczbę dni. Kontakt pomiędzy graczami do tego czasu jest zablokowany.",
+	"vcmi.optionsTab.simturnsMax.help" : "Graj równocześnie przez określoną liczbę dni lub do momentu napotkania innego gracza.",
+	"vcmi.optionsTab.simturnsAI.help" : "{Równoczesne tury graczy AI}\nOpcja eksperymentalna. Pozwala graczom sterowanym przez komputer wykonywać akcje w tym samym czasie co gracz ludzki gdy jednoczesne tury są włączone.",
+
+	"vcmi.optionsTab.turnTime.select"     : "Predefiniowane schematy zegarów",
+	"vcmi.optionsTab.turnTime.unlimited"  : "Nieograniczony czas tury",
+	"vcmi.optionsTab.turnTime.classic.1"  : "Klasyczny: 1 minuta",
+	"vcmi.optionsTab.turnTime.classic.2"  : "Klasyczny: 2 minuty",
+	"vcmi.optionsTab.turnTime.classic.5"  : "Klasyczny: 5 minut",
+	"vcmi.optionsTab.turnTime.classic.10" : "Klasyczny: 10 minut",
+	"vcmi.optionsTab.turnTime.classic.20" : "Klasyczny: 20 minut",
+	"vcmi.optionsTab.turnTime.classic.30" : "Klasyczny: 30 minut",
+	"vcmi.optionsTab.turnTime.chess.20"   : "Szach: 20:00 + 10:00 + 02:00 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.16"   : "Szach: 16:00 + 08:00 + 01:30 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.8"    : "Szach: 08:00 + 04:00 + 01:00 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.4"    : "Szach: 04:00 + 02:00 + 00:30 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.2"    : "Szach: 02:00 + 01:00 + 00:15 + 00:00",
+	"vcmi.optionsTab.turnTime.chess.1"    : "Szach: 01:00 + 01:00 + 00:00 + 00:00",
+
+	"vcmi.optionsTab.simturns.select"         : "Predefiniowane schematy tur sym.",
+	"vcmi.optionsTab.simturns.none"           : "Brak tur symultanicznych / równocz.",
+	"vcmi.optionsTab.simturns.tillContactMax" : "Tury sym.: Do kontaktu",
+	"vcmi.optionsTab.simturns.tillContact1"   : "Tury sym.: 1 tydz., przerw. przy kontakcie",
+	"vcmi.optionsTab.simturns.tillContact2"   : "Tury sym.: 2 tyg., przerw. przy kontakcie",
+	"vcmi.optionsTab.simturns.tillContact4"   : "Tury sym.: 1 mies., przerw. przy kontakcie",
+	"vcmi.optionsTab.simturns.blocked1"       : "Tury sym.: 1 tydz., kontakt zablokowany",
+	"vcmi.optionsTab.simturns.blocked2"       : "Tury sym.: 2 tyg., kontakt zablokowany",
+	"vcmi.optionsTab.simturns.blocked4"       : "Tury sym.: 1 mies., kontakt zablokowany",
+
+	// 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 dni",
+	"vcmi.optionsTab.simturns.days.1" : " %d dzień",
+	"vcmi.optionsTab.simturns.days.2" : " %d dni",
+	"vcmi.optionsTab.simturns.weeks.0" : " %d tygodni",
+	"vcmi.optionsTab.simturns.weeks.1" : " %d tydzień",
+	"vcmi.optionsTab.simturns.weeks.2" : " %d tygodnie",
+	"vcmi.optionsTab.simturns.months.0" : " %d miesięcy",
+	"vcmi.optionsTab.simturns.months.1" : " %d miesiąc",
+	"vcmi.optionsTab.simturns.months.2" : " %d miesiące",
+
 	// Custom victory conditions for H3 campaigns and HotA maps
 	"vcmi.map.victoryCondition.daysPassed.toOthers" : "Wróg dał radę przetrwać do dzisiejszego dnia. Zwycięstwo należy do niego!",
 	"vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulacje! Dałeś radę przetrwać. Zwycięstwo jest twoje!",

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

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

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

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

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

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

+ 11 - 5
client/CServerHandler.cpp

@@ -50,6 +50,7 @@
 #include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
 #include "../lib/serializer/Connection.h"
 #include "../lib/serializer/CMemorySerializer.h"
+#include "../lib/UnlockGuard.h"
 
 #include <boost/uuid/uuid.hpp>
 #include <boost/uuid/uuid_io.hpp>
@@ -417,8 +418,12 @@ void CServerHandler::sendClientDisconnecting()
 	}
 	sendLobbyPack(lcd);
 	
-	c->close();
-	c.reset();
+	{
+		// Network thread might be applying network pack at this moment
+		auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
+		c->close();
+		c.reset();
+	}
 }
 
 void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign)
@@ -665,9 +670,6 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
 
 void CServerHandler::endGameplay(bool closeConnection, bool restart)
 {
-	client->endGame();
-	vstd::clear_pointer(client);
-
 	if(closeConnection)
 	{
 		// Game is ending
@@ -675,6 +677,10 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart)
 		CSH->sendClientDisconnecting();
 		logNetwork->info("Closed connection.");
 	}
+
+	client->endGame();
+	vstd::clear_pointer(client);
+
 	if(!restart)
 	{
 		if(CMM)

+ 2 - 1
client/HeroMovementController.cpp

@@ -100,7 +100,8 @@ void HeroMovementController::showTeleportDialog(const CGHeroInstance * hero, Tel
 		}
 	}
 
-	assert(0); // exit not found? How?
+	// may happen when hero has path but does not moves alongside it
+	// for example, while standing on teleporter set path that does not leads throught teleporter and press space
 	LOCPLINT->cb->selectionMade(-1, askID);
 	return;
 }

+ 1 - 1
client/NetPacksClient.cpp

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

+ 4 - 1
client/adventureMap/AdventureMapInterface.cpp

@@ -441,7 +441,10 @@ void AdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID)
 		if(auto iw = GH.windows().topWindow<CInfoWindow>())
 			iw->close();
 
-		hotkeyEndingTurn();
+		GH.dispatchMainThread([this]()
+		{
+			hotkeyEndingTurn();
+		});
 	}
 }
 

+ 2 - 2
client/adventureMap/TurnTimerWidget.cpp

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

+ 1 - 1
client/battle/BattleActionsController.cpp

@@ -999,7 +999,7 @@ void BattleActionsController::onHexRightClicked(BattleHex clickedHex)
 		return action.spellcast();
 	};
 
-	bool isCurrentStackInSpellcastMode = std::all_of(possibleActions.begin(), possibleActions.end(), spellcastActionPredicate);
+	bool isCurrentStackInSpellcastMode = !possibleActions.empty() && std::all_of(possibleActions.begin(), possibleActions.end(), spellcastActionPredicate);
 
 	if (spellcastingModeActive() || isCurrentStackInSpellcastMode)
 	{

+ 1 - 1
client/battle/BattleStacksController.cpp

@@ -534,7 +534,7 @@ void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleH
 		addNewAnim(new MovementStartAnimation(owner, stack));
 	});
 
-	if (!stack->hasBonus(Selector::typeSubtype(BonusType::FLYING, BonusCustomSubtype::movementFlying)))
+	if (!stack->hasBonus(Selector::typeSubtype(BonusType::FLYING, BonusCustomSubtype::movementTeleporting)))
 	{
 		owner.addToAnimationStage(EAnimationEvents::MOVEMENT, [&]()
 		{

+ 3 - 3
client/gui/InterfaceObjectConfigurable.cpp

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

+ 10 - 2
client/lobby/OptionsTab.cpp

@@ -42,8 +42,16 @@
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/mapping/CMapHeader.h"
 
+static JsonPath optionsTabConfigLocation()
+{
+	if(settings["general"]["enableUiEnhancements"].Bool())
+		return JsonPath::builtin("config/widgets/playerOptionsTab.json");
+	else
+		return JsonPath::builtin("config/widgets/advancedOptionsTab.json");
+}
+
 OptionsTab::OptionsTab()
-	: OptionsTabBase(JsonPath::builtin("config/widgets/playerOptionsTab.json"))
+	: OptionsTabBase(optionsTabConfigLocation())
 	, humanPlayers(0)
 {
 }
@@ -190,7 +198,7 @@ std::string OptionsTab::CPlayerSettingsHelper::getName()
 					return CGI->generaltexth->allTexts[522];
 
 			if(!playerSettings.heroNameTextId.empty())
-				return playerSettings.heroNameTextId;
+				return CGI->generaltexth->translate(playerSettings.heroNameTextId);
 			auto index = playerSettings.getHeroValidated();
 			return (*CGI->heroh)[index]->getNameTranslated();
 		}

+ 112 - 19
client/lobby/OptionsTabBase.cpp

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

+ 10 - 0
client/lobby/OptionsTabBase.h

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

+ 2 - 2
client/mainmenu/CMainMenu.cpp

@@ -528,9 +528,11 @@ CSimpleJoinScreen::CSimpleJoinScreen(bool host)
 	textTitle = std::make_shared<CTextBox>("", Rect(20, 20, 205, 50), 0, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE);
 	inputAddress = std::make_shared<CTextInput>(Rect(25, 68, 175, 16), background->getSurface());
 	inputPort = std::make_shared<CTextInput>(Rect(25, 115, 175, 16), background->getSurface());
+	buttonOk = std::make_shared<CButton>(Point(26, 142), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::connectToServer, this), EShortcut::GLOBAL_ACCEPT);
 	if(host && !settings["session"]["donotstartserver"].Bool())
 	{
 		textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverConnecting"));
+		buttonOk->block(true);
 		startConnectThread();
 	}
 	else
@@ -539,8 +541,6 @@ CSimpleJoinScreen::CSimpleJoinScreen(bool host)
 		inputAddress->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
 		inputPort->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
 		inputPort->filters += std::bind(&CTextInput::numberFilter, _1, _2, 0, 65535);
-		buttonOk = std::make_shared<CButton>(Point(26, 142), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::connectToServer, this), EShortcut::GLOBAL_ACCEPT);
-
 		inputAddress->giveFocus();
 	}
 	inputAddress->setText(host ? CServerHandler::localhostAddress : CSH->getHostAddress(), true);

+ 1 - 0
client/widgets/CGarrisonInt.cpp

@@ -432,6 +432,7 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, EGa
 
 	selectionImage = std::make_shared<CAnimImage>(graphics->getAnimation(imgName), 1);
 	selectionImage->disable();
+	selectionImage->center(creatureImage->pos.center());
 
 	if(Owner->smallIcons)
 	{

+ 27 - 23
client/widgets/ComboBox.cpp

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

+ 6 - 3
client/widgets/ComboBox.h

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

+ 2 - 4
client/widgets/Images.cpp

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

+ 0 - 4
client/widgets/Images.h

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

+ 2 - 0
client/widgets/TextControls.cpp

@@ -376,6 +376,7 @@ void CTextBox::setText(const std::string & text)
 	{
 		// decrease width again if slider still used
 		label->pos.w = pos.w - 32;
+		assert(label->pos.w > 0);
 		label->setText(text);
 		slider->setAmount(label->textSize.y);
 	}
@@ -383,6 +384,7 @@ void CTextBox::setText(const std::string & text)
 	{
 		// create slider and update widget
 		label->pos.w = pos.w - 32;
+		assert(label->pos.w > 0);
 		label->setText(text);
 
 		OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);

+ 2 - 2
client/windows/CCastleInterface.cpp

@@ -901,7 +901,7 @@ void CCastleBuildings::enterDwelling(int level)
 	{
 		LOCPLINT->cb->recruitCreatures(town, town->getUpperArmy(), id, count, level);
 	};
-	GH.windows().createAndPushWindow<CRecruitmentWindow>(town, level, town, recruitCb, nullptr, -87);
+	GH.windows().createAndPushWindow<CRecruitmentWindow>(town, level, town->getUpperArmy(), recruitCb, nullptr, -87);
 }
 
 void CCastleBuildings::enterToTheQuickRecruitmentWindow()
@@ -1110,7 +1110,7 @@ void CCreaInfo::clickPressed(const Point & cursorPosition)
 	{
 		LOCPLINT->cb->recruitCreatures(town, town->getUpperArmy(), id, count, level);
 	};
-	GH.windows().createAndPushWindow<CRecruitmentWindow>(town, level, town, recruitCb, nullptr, offset);
+	GH.windows().createAndPushWindow<CRecruitmentWindow>(town, level, town->getUpperArmy(), recruitCb, nullptr, offset);
 }
 
 std::string CCreaInfo::genGrowthText()

+ 4 - 0
client/windows/CMessage.cpp

@@ -120,6 +120,10 @@ SDL_Surface * CMessage::drawDialogBox(int w, int h, PlayerColor playerColor)
 
 std::vector<std::string> CMessage::breakText( std::string text, size_t maxLineWidth, EFonts font )
 {
+	assert(maxLineWidth != 0);
+	if (maxLineWidth == 0)
+		return { text };
+
 	std::vector<std::string> ret;
 
 	boost::algorithm::trim_right_if(text,boost::algorithm::is_any_of(std::string(" ")));

+ 20 - 5
client/windows/CSpellWindow.cpp

@@ -97,13 +97,13 @@ public:
 } spellsorter;
 
 CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _myInt, bool openOnBattleSpells):
-	CWindowObject(PLAYER_COLORED | (settings["general"]["enableUiEnhancements"].Bool() ? BORDERED : 0)),
+	CWindowObject(PLAYER_COLORED | (settings["gameTweaks"]["enableLargeSpellbook"].Bool() ? BORDERED : 0)),
 	battleSpellsOnly(openOnBattleSpells),
 	selectedTab(4),
 	currentPage(0),
 	myHero(_myHero),
 	myInt(_myInt),
-	isBigSpellbook(settings["general"]["enableUiEnhancements"].Bool()),
+	isBigSpellbook(settings["gameTweaks"]["enableLargeSpellbook"].Bool()),
 	spellsPerPage(24),
 	offL(-11),
 	offR(195),
@@ -449,8 +449,16 @@ void CSpellWindow::setCurrentPage(int value)
 	schoolPicture->visible = selectedTab!=4 && currentPage == 0;
 	if(selectedTab != 4)
 		schoolPicture->setFrame(selectedTab, 0);
-	leftCorner->visible = currentPage != 0;
-	rightCorner->visible = (currentPage+1) < pagesWithinCurrentTab();
+
+	if (currentPage != 0)
+		leftCorner->enable();
+	else
+		leftCorner->disable();
+
+	if (currentPage + 1 < pagesWithinCurrentTab())
+		rightCorner->enable();
+	else
+		rightCorner->disable();
 
 	mana->setText(std::to_string(myHero->mana));//just in case, it will be possible to cast spell without closing book
 }
@@ -648,7 +656,14 @@ void CSpellWindow::SpellArea::setSpell(const CSpell * spell)
 
 		{
 			OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-			schoolBorder = std::make_shared<CAnimImage>(owner->schoolBorders[owner->selectedTab >= 4 ? whichSchool.getNum() : owner->selectedTab], schoolLevel);
+			schoolBorder.reset();
+			if (owner->selectedTab >= 4)
+			{
+				if (whichSchool.getNum() != SpellSchool())
+					schoolBorder = std::make_shared<CAnimImage>(owner->schoolBorders.at(whichSchool.getNum()), schoolLevel);
+			}
+			else
+				schoolBorder = std::make_shared<CAnimImage>(owner->schoolBorders.at(owner->selectedTab), schoolLevel);
 		}
 
 		ColorRGBA firstLineColor, secondLineColor;

+ 17 - 1
client/windows/CTradeWindow.cpp

@@ -137,7 +137,23 @@ std::vector<int> *CTradeWindow::getItemsIds(bool Left)
 {
 	std::vector<int> *ids = nullptr;
 
-	if(!Left)
+	if(Left)
+	{
+		switch(itemsType[1])
+		{
+		case CREATURE:
+			ids = new std::vector<int>;
+			for(int i = 0; i < 7; i++)
+			{
+				if(const CCreature *c = hero->getCreature(SlotID(i)))
+					ids->push_back(c->getId());
+				else
+					ids->push_back(-1);
+			}
+			break;
+		}
+	}
+	else
 	{
 		switch(itemsType[0])
 		{

+ 1 - 1
client/windows/GUIClasses.cpp

@@ -159,7 +159,7 @@ void CRecruitmentWindow::buy()
 		else
 		{
 			std::string txt;
-			if(dst->ID == Obj::HERO)
+			if(dwelling->ID != Obj::TOWN)
 			{
 				txt = CGI->generaltexth->allTexts[425]; //The %s would join your hero, but there aren't enough provisions to support them.
 				boost::algorithm::replace_first(txt, "%s", slider->getValue() > 1 ? CGI->creh->objects[crid]->getNamePluralTranslated() : CGI->creh->objects[crid]->getNameSingularTranslated());

+ 3 - 1
client/windows/InfoWindows.cpp

@@ -150,7 +150,9 @@ CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo
 	text = std::make_shared<CTextBox>(Text, Rect(0, 0, 250, 100), 0, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE);
 	if(!text->slider)
 	{
-		text->resize(text->label->textSize);
+		int finalWidth = std::min(250, text->label->textSize.x + 32);
+		int finalHeight = text->label->textSize.y;
+		text->resize(Point(finalWidth, finalHeight));
 	}
 
 	if(buttons.size() == 1)

+ 9 - 0
client/windows/settings/GeneralOptionsTab.cpp

@@ -162,6 +162,11 @@ GeneralOptionsTab::GeneralOptionsTab()
 		setBoolSetting("general", "enableUiEnhancements", value);
 	});
 
+	addCallback("enableLargeSpellbookChanged", [](bool value)
+	{
+		setBoolSetting("gameTweaks", "enableLargeSpellbook", value);
+	});
+
 	//moved from "other" tab that is disabled for now to avoid excessible tabs with barely any content
 	addCallback("availableCreaturesAsDwellingChanged", [=](int value)
 	{
@@ -206,6 +211,10 @@ GeneralOptionsTab::GeneralOptionsTab()
 	if (enableUiEnhancementsCheckbox)
 		enableUiEnhancementsCheckbox->setSelected(settings["general"]["enableUiEnhancements"].Bool());
 
+	std::shared_ptr<CToggleButton> enableLargeSpellbookCheckbox = widget<CToggleButton>("enableLargeSpellbookCheckbox");
+	if (enableLargeSpellbookCheckbox)
+		enableLargeSpellbookCheckbox->setSelected(settings["gameTweaks"]["enableLargeSpellbook"].Bool());
+
 	std::shared_ptr<CSlider> musicSlider = widget<CSlider>("musicSlider");
 	musicSlider->scrollTo(CCS->musich->getVolume());
 

+ 5 - 5
config/heroes/stronghold.json

@@ -237,13 +237,13 @@
 		],
 		"specialty" : {
 			"bonuses" : {
-				"sorcery" : {
-					"targetSourceType" : "SECONDARY_SKILL",
-					"type" : "SPELL_DAMAGE",
-					"subtype" : "spellSchool.any",
+				"offence" : {
+					"subtype" : "damageTypeMelee",
+					"type" : "PERCENTAGE_DAMAGE_BOOST",
 					"updater" : "TIMES_HERO_LEVEL",
 					"val" : 5,
-					"valueType" : "PERCENT_TO_TARGET_TYPE"
+					"valueType" : "PERCENT_TO_TARGET_TYPE",
+					"targetSourceType" : "SECONDARY_SKILL"
 				}
 			}
 		}

+ 21 - 1
config/objects/generic.json

@@ -10,7 +10,11 @@
 			}
 		},
 		"types" : {
-			"prison" : { "index" : 0, "aiValue" : 5000 }
+			"prison" : {
+				"index" : 0,
+				"aiValue" : 5000,
+				"removable": true
+			}
 		}
 	},
 
@@ -136,6 +140,7 @@
 			"object" : {
 				"index" : 0,
 				"aiValue" : 10000,
+				"removable": true,
 				"templates" : {
 					"normal" : { "animation" : "ava0128.def", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] }
 				},
@@ -150,6 +155,7 @@
 		"types" : {
 			"object" : {
 				"index" : 0,
+				"removable": true,
 				"rmg" : {
 				}
 			}
@@ -313,6 +319,7 @@
 			"object" : {
 				"index" : 0,
 				"aiValue" : 0,
+				"removable": true,
 				"rmg" : {
 				}
 			}
@@ -444,6 +451,7 @@
 			"object" : {
 				"index" : 0,
 				"aiValue" : 10000,
+				"removable": true,
 				"rmg" : {
 				}
 			}
@@ -495,6 +503,7 @@
 		"types" : {
 			"object" : {
 				"index" : 0,
+				"removable": true,
 				"rmg" : {
 					"value"		: 2000,
 					"rarity"	: 150
@@ -511,6 +520,7 @@
 		"types" : {
 			"object" : {
 				"index" : 0,
+				"removable": true,
 				"rmg" : {
 					"value"		: 5000,
 					"rarity"	: 150
@@ -527,6 +537,7 @@
 		"types" : {
 			"object" : {
 				"index" : 0,
+				"removable": true,
 				"rmg" : {
 					"value"		: 10000,
 					"rarity"	: 150
@@ -543,6 +554,7 @@
 		"types" : {
 			"object" : {
 				"index" : 0,
+				"removable": true,
 				"rmg" : {
 					"value"		: 20000,
 					"rarity"	: 150
@@ -572,6 +584,7 @@
 		"types" : {
 			"object" : {
 				"index" : 0,
+				"removable": true,
 				"templates" : {
 					"normal" : { "animation" : "AVWmon1", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] }
 				}
@@ -584,6 +597,7 @@
 		"types" : {
 			"object" : {
 				"index" : 0,
+				"removable": true,
 				"templates" : {
 					"normal" : { "animation" : "AVWmon2", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] }
 				}
@@ -596,6 +610,7 @@
 		"types" : {
 			"object" : {
 				"index" : 0,
+				"removable": true,
 				"templates" : {
 					"normal" : { "animation" : "AVWmon3", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] }
 				}
@@ -608,6 +623,7 @@
 		"types" : {
 			"object" : {
 				"index" : 0,
+				"removable": true,
 				"templates" : {
 					"normal" : { "animation" : "AVWmon4", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] }
 				}
@@ -632,6 +648,7 @@
 		"types" : {
 			"object" : {
 				"index" : 0,
+				"removable": true,
 				"templates" : {
 					"normal" : { "animation" : "AVWmon6", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] }
 				}
@@ -644,6 +661,7 @@
 		"types" : {
 			"object" : {
 				"index" : 0,
+				"removable": true,
 				"templates" : {
 					"normal" : { "animation" : "AVWmon7", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] }
 				}
@@ -687,6 +705,7 @@
 			"object" : {
 				"index" : 0,
 				"aiValue" : 0,
+				"removable": true,
 				"rmg" : {
 				}
 			}
@@ -723,6 +742,7 @@
 		"index" :95,
 		"handler": "generic",
 		"base" : {
+			"blockVisit": true,
 			"sounds" : {
 				"ambient" : ["LOOPTAV"],
 				"visit" : ["STORE"]

+ 7 - 0
config/objects/moddables.json

@@ -8,6 +8,7 @@
 		"index" :5, 
 		"handler": "artifact",
 		"base" : {
+			"removable": true,
 			"base" : {
 				"visitableFrom" : [ "+++", "+-+", "+++" ],
 				"mask" : [ "VV", "VA"]
@@ -25,6 +26,7 @@
 		"handler": "hero",
 		"base" : {
 			"aiValue" : 7500,
+			"removable": true,
 			"base" : {
 				"visitableFrom" : [ "+++", "+-+", "+++" ],
 				"mask" : [ "VVV", "VAV"]
@@ -40,6 +42,7 @@
 		"index" :54,
 		"handler": "monster",
 		"base" : {
+			"removable": true,
 			"base" : {
 				"visitableFrom" : [ "+++", "+-+", "+++" ],
 				"mask" : [ "VV", "VA"]
@@ -55,6 +58,7 @@
 		"index" :76,
 		"handler": "randomResource",
 		"base" : {
+			"removable": true,
 			"base" : {
 				"visitableFrom" : [ "+++", "+-+", "+++" ],
 				"mask" : [ "VA" ]
@@ -85,6 +89,7 @@
 		"handler": "resource",
 		"lastReservedIndex" : 6,
 		"base" : {
+			"removable": true,
 			"base" : {
 				"visitableFrom" : [ "+++", "+-+", "+++" ],
 				"mask" : [ "VA" ]
@@ -138,6 +143,7 @@
 		"lastReservedIndex" : 2,
 		"base" : {
 			"aiValue" : 0,
+			"removable": true,
 			"layer" : "sail",
 			"onboardAssaultAllowed" : true,
 			"onboardVisitAllowed" : true,
@@ -175,6 +181,7 @@
 		"lastReservedIndex" : 7,
 		"base" : {
 			"aiValue" : 0,
+			"removable": true,
 			"sounds" : {
 				"visit" : ["CAVEHEAD"],
 				"removal" : [ "PICKUP01", "PICKUP02", "PICKUP03", "PICKUP04", "PICKUP05", "PICKUP06", "PICKUP07" ]

+ 1 - 2
config/objects/rewardableOnceVisitable.json

@@ -41,6 +41,7 @@
 		"index" : 22,
 		"handler": "configurable",
 		"base" : {
+			"blockedVisitable" : true,
 			"sounds" : {
 				"visit" : ["MYSTERY"]
 			}
@@ -54,9 +55,7 @@
 					"rarity"	: 100
 				},
 				"compatibilityIdentifiers" : [ "object" ],
-
 				"onVisitedMessage" : 38,
-				"blockedVisitable" : true,
 				"visitMode" : "once",
 				"selectMode" : "selectFirst",
 				"rewards" : [

+ 10 - 9
config/objects/rewardablePickable.json

@@ -5,6 +5,8 @@
 		"index" : 12,
 		"handler": "configurable",
 		"base" : {
+			"blockedVisitable" : true,
+			"removable": true,
 			"sounds" : {
 				"ambient" : ["LOOPCAMP"],
 				"visit" : ["EXPERNCE"],
@@ -20,8 +22,6 @@
 					"rarity"	: 500
 				},
 				"compatibilityIdentifiers" : [ "object" ],
-
-				"blockedVisitable" : true,
 				"visitMode" : "unlimited",
 				"selectMode" : "selectFirst",
 				"rewards" : [
@@ -48,6 +48,8 @@
 		"index" : 29,
 		"handler": "configurable",
 		"base" : {
+			"blockedVisitable" : true,
+			"removable": true,
 			"sounds" : {
 				"visit" : ["GENIE"],
 				"removal" : [ "PICKUP01", "PICKUP02", "PICKUP03", "PICKUP04", "PICKUP05", "PICKUP06", "PICKUP07" ]
@@ -62,15 +64,13 @@
 					"rarity"	: 100
 				},
 				"compatibilityIdentifiers" : [ "object" ],
-
-				"blockedVisitable" : true,
 				"visitMode" : "unlimited",
 				"selectMode" : "selectFirst",
 				"rewards" : [
 					{
 						"message" : 51,
 						"appearChance" : { "max" : 25 },
-						"removeObject" : true,
+						"removeObject" : true
 					},
 					{
 						"message" : 52,
@@ -106,6 +106,8 @@
 		"index" : 82,
 		"handler": "configurable",
 		"base" : {
+			"blockedVisitable" : true,
+			"removable": true,
 			"sounds" : {
 				"visit" : ["CHEST"],
 				"removal" : [ "PICKUP01", "PICKUP02", "PICKUP03", "PICKUP04", "PICKUP05", "PICKUP06", "PICKUP07" ]
@@ -120,8 +122,6 @@
 					"rarity"	: 500
 				},
 				"compatibilityIdentifiers" : [ "object" ],
-
-				"blockedVisitable" : true,
 				"visitMode" : "unlimited",
 				"selectMode" : "selectFirst",
 				"rewards" : [
@@ -157,6 +157,8 @@
 		"index" : 86,
 		"handler": "configurable",
 		"base" : {
+			"blockedVisitable" : true,
+			"removable": true,
 			"sounds" : {
 				"visit" : ["TREASURE"],
 				"removal" : [ "PICKUP01", "PICKUP02", "PICKUP03", "PICKUP04", "PICKUP05", "PICKUP06", "PICKUP07" ]
@@ -171,8 +173,6 @@
 					"rarity"	: 50
 				},
 				"compatibilityIdentifiers" : [ "object" ],
-
-				"blockedVisitable" : true,
 				"visitMode" : "unlimited",
 				"selectMode" : "selectFirst",
 				"rewards" : [
@@ -208,6 +208,7 @@
 		"index" : 101,
 		"handler": "configurable",
 		"base" : {
+			"removable": true,
 			"sounds" : {
 				"visit" : ["CHEST"],
 				"removal" : [ "PICKUP01", "PICKUP02", "PICKUP03", "PICKUP04", "PICKUP05", "PICKUP06", "PICKUP07" ]

+ 6 - 1
config/schemas/settings.json

@@ -576,7 +576,8 @@
 				"compactTownCreatureInfo",
 				"infoBarPick",
 				"skipBattleIntroMusic",
-				"infoBarCreatureManagement"
+				"infoBarCreatureManagement",
+				"enableLargeSpellbook"
 			],
 			"properties" : {
 				"showGrid" : {
@@ -610,6 +611,10 @@
 				"infoBarCreatureManagement": {
 					"type" : "boolean",
 					"default" : false
+				},
+				"enableLargeSpellbook" : {
+					"type": "boolean",
+					"default": false
 				}
 			}
 		}

+ 129 - 0
config/widgets/advancedOptionsTab.json

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

+ 35 - 43
config/widgets/playerOptionsTab.json

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

+ 0 - 1
config/widgets/settings/battleOptionsTab.json

@@ -69,7 +69,6 @@
 			[
 				{
 					"name": "enableAutocombatSpellsCheckbox",
-					"help": "vcmi.battleOptions.enableAutocombatSpells",
 					"callback": "enableAutocombatSpellsChanged"
 				}
 			]

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

@@ -50,6 +50,9 @@
 				{
 					"text": "vcmi.systemOptions.framerateButton.hover"
 				},
+				{
+					"text": "vcmi.systemOptions.enableLargeSpellbookButton.hover"
+				},
 				{
 					"text": "core.genrltxt.577"
 				},
@@ -102,6 +105,11 @@
 					"help": "vcmi.systemOptions.framerateButton",
 					"callback": "framerateChanged"
 				},
+				{
+					"name": "enableLargeSpellbookCheckbox",
+					"help": "vcmi.systemOptions.enableLargeSpellbookButton",
+					"callback": "enableLargeSpellbookChanged"
+				},
 				{
 					"name": "spellbookAnimationCheckbox",
 					"help": "core.help.364",

+ 520 - 0
config/widgets/turnOptionsDropdownLibrary.json

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

+ 92 - 12
config/widgets/turnOptionsTab.json

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

+ 1 - 1
debian/changelog

@@ -2,7 +2,7 @@ vcmi (1.4.0) jammy; urgency=medium
 
   * New upstream release
 
- -- Ivan Savenko <[email protected]>  Fri, 22 Dec 2023 16:00:00 +0200
+ -- Ivan Savenko <[email protected]>  Fri, 8 Dec 2023 16:00:00 +0200
 
 vcmi (1.3.2) jammy; urgency=medium
 

+ 1 - 3
docs/Readme.md

@@ -1,7 +1,5 @@
 [![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.3.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.3.0)
-[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.3.1/total)](https://github.com/vcmi/vcmi/releases/tag/1.3.1)
-[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.3.2/total)](https://github.com/vcmi/vcmi/releases/tag/1.3.2)
+[![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/total)](https://github.com/vcmi/vcmi/releases)
 
 # VCMI Project

+ 1 - 1
launcher/eu.vcmi.VCMI.metainfo.xml

@@ -68,7 +68,7 @@
 		<category>StrategyGame</category>
 	</categories>
 	<releases>
-		<release version="1.4.0" date="2023-12-22" type="development" />
+		<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" />

+ 5 - 5
launcher/translation/chinese.ts

@@ -669,27 +669,27 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use
         <translation>显示开场动画</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="407"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="409"/>
         <source>Active</source>
         <translation>激活</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="412"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="414"/>
         <source>Disabled</source>
         <translation>禁用</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="413"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="415"/>
         <source>Enable</source>
         <translation>启用</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="418"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="420"/>
         <source>Not Installed</source>
         <translation>未安装</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="419"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="421"/>
         <source>Install</source>
         <translation>安装</translation>
     </message>

+ 5 - 5
launcher/translation/english.ts

@@ -667,27 +667,27 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="407"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="409"/>
         <source>Active</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="412"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="414"/>
         <source>Disabled</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="413"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="415"/>
         <source>Enable</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="418"/>
+        <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="419"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="421"/>
         <source>Install</source>
         <translation type="unfinished"></translation>
     </message>

+ 5 - 5
launcher/translation/french.ts

@@ -678,27 +678,27 @@ Mode exclusif plein écran - le jeu couvrira l&quot;intégralité de votre écra
         <translation>Montrer l&apos;intro</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="407"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="409"/>
         <source>Active</source>
         <translation>Actif</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="412"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="414"/>
         <source>Disabled</source>
         <translation>Désactivé</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="413"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="415"/>
         <source>Enable</source>
         <translation>Activé</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="418"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="420"/>
         <source>Not Installed</source>
         <translation>Pas Installé</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="419"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="421"/>
         <source>Install</source>
         <translation>Installer</translation>
     </message>

+ 24 - 17
launcher/translation/german.ts

@@ -121,7 +121,7 @@
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="48"/>
         <source>Maps</source>
-        <translation type="unfinished"></translation>
+        <translation>Karten</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="49"/>
@@ -180,7 +180,7 @@
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="62"/>
         <source>Compatibility</source>
-        <translation type="unfinished">Kompatibilität</translation>
+        <translation>Kompatibilität</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="63"/>
@@ -319,7 +319,7 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="253"/>
         <source>Size</source>
-        <translation type="unfinished"></translation>
+        <translation>Größe</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="255"/>
@@ -410,12 +410,12 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="599"/>
         <source>Downloading %s%. %p% (%v MB out of %m MB) finished</source>
-        <translation type="unfinished"></translation>
+        <translation>Herunterladen von %s%. %p% (%v MB von %m MB) beendet</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="618"/>
         <source>Download failed</source>
-        <translation type="unfinished"></translation>
+        <translation>Download fehlgeschlagen</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="619"/>
@@ -424,30 +424,37 @@
 Encountered errors:
 
 </source>
-        <translation type="unfinished"></translation>
+        <translation>Es konnten nicht alle Dateien heruntergeladen werden.
+
+Es sind Fehler aufgetreten:
+
+</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="620"/>
         <source>
 
 Install successfully downloaded?</source>
-        <translation type="unfinished"></translation>
+        <translation>
+
+Installation erfolgreich heruntergeladen?</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="759"/>
         <source>Installing mod %1</source>
-        <translation type="unfinished"></translation>
+        <translation>Installation von Mod %1</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="811"/>
         <source>Operation failed</source>
-        <translation type="unfinished"></translation>
+        <translation>Operation fehlgeschlagen</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="812"/>
         <source>Encountered errors:
 </source>
-        <translation type="unfinished"></translation>
+        <translation>Aufgetretene Fehler:
+</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="847"/>
@@ -598,7 +605,7 @@ Exklusiver Vollbildmodus - das Spiel bedeckt den gesamten Bildschirm und verwend
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="631"/>
         <source>Reserved screen area</source>
-        <translation type="unfinished"></translation>
+        <translation>Reservierter Bildschirmbereich</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="533"/>
@@ -649,7 +656,7 @@ Exklusiver Vollbildmodus - das Spiel bedeckt den gesamten Bildschirm und verwend
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="662"/>
         <source>VSync</source>
-        <translation type="unfinished"></translation>
+        <translation>VSync</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="374"/>
@@ -703,27 +710,27 @@ Exklusiver Vollbildmodus - das Spiel bedeckt den gesamten Bildschirm und verwend
     <message>
         <location filename="../lobby/chat_moc.ui" line="14"/>
         <source>Form</source>
-        <translation type="unfinished"></translation>
+        <translation>Formular</translation>
     </message>
     <message>
         <location filename="../lobby/chat_moc.ui" line="40"/>
         <source>Users in lobby</source>
-        <translation type="unfinished"></translation>
+        <translation>Benutzer in der Lobby</translation>
     </message>
     <message>
         <location filename="../lobby/chat_moc.ui" line="50"/>
         <source>Global chat</source>
-        <translation type="unfinished"></translation>
+        <translation>Globaler Chat</translation>
     </message>
     <message>
         <location filename="../lobby/chat_moc.ui" line="104"/>
         <source>type you message</source>
-        <translation type="unfinished"></translation>
+        <translation>Nachricht eingeben</translation>
     </message>
     <message>
         <location filename="../lobby/chat_moc.ui" line="111"/>
         <source>send</source>
-        <translation type="unfinished"></translation>
+        <translation>senden</translation>
     </message>
 </context>
 <context>

+ 47 - 40
launcher/translation/polish.ts

@@ -121,7 +121,7 @@
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="48"/>
         <source>Maps</source>
-        <translation type="unfinished"></translation>
+        <translation>Mapy</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="49"/>
@@ -180,7 +180,7 @@
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="62"/>
         <source>Compatibility</source>
-        <translation type="unfinished">Kompatybilność</translation>
+        <translation>Kompatybilność</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="63"/>
@@ -319,7 +319,7 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="253"/>
         <source>Size</source>
-        <translation type="unfinished"></translation>
+        <translation>Rozmiar</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="255"/>
@@ -410,12 +410,12 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="599"/>
         <source>Downloading %s%. %p% (%v MB out of %m MB) finished</source>
-        <translation type="unfinished"></translation>
+        <translation>Pobieranie %s%. %p% (%v MB z %m MB) ukończono</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="618"/>
         <source>Download failed</source>
-        <translation type="unfinished"></translation>
+        <translation>Pobieranie nieudane</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="619"/>
@@ -424,30 +424,37 @@
 Encountered errors:
 
 </source>
-        <translation type="unfinished"></translation>
+        <translation>Nie udało się pobrać wszystkich plików.
+
+Napotkane błędy:
+
+</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="620"/>
         <source>
 
 Install successfully downloaded?</source>
-        <translation type="unfinished"></translation>
+        <translation>
+
+Zainstalować pomyślnie pobrane?</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="759"/>
         <source>Installing mod %1</source>
-        <translation type="unfinished"></translation>
+        <translation>Instalowanie modyfikacji %1</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="811"/>
         <source>Operation failed</source>
-        <translation type="unfinished"></translation>
+        <translation>Operacja nieudana</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="812"/>
         <source>Encountered errors:
 </source>
-        <translation type="unfinished"></translation>
+        <translation>Napotkane błędy:
+</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="847"/>
@@ -598,7 +605,7 @@ Pełny ekran klasyczny - gra przysłoni cały ekran uruchamiając się w wybrane
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="631"/>
         <source>Reserved screen area</source>
-        <translation type="unfinished"></translation>
+        <translation>Zarezerwowany obszar ekranu</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="533"/>
@@ -649,7 +656,7 @@ Pełny ekran klasyczny - gra przysłoni cały ekran uruchamiając się w wybrane
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="662"/>
         <source>VSync</source>
-        <translation type="unfinished"></translation>
+        <translation>Synchronizacja pionowa (VSync)</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="374"/>
@@ -673,27 +680,27 @@ Pełny ekran klasyczny - gra przysłoni cały ekran uruchamiając się w wybrane
         <translation>Pokaż intro</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="407"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="409"/>
         <source>Active</source>
         <translation>Aktywny</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="412"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="414"/>
         <source>Disabled</source>
         <translation>Wyłączone</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="413"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="415"/>
         <source>Enable</source>
         <translation>Włącz</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="418"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="420"/>
         <source>Not Installed</source>
         <translation>Nie zainstalowano</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="419"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="421"/>
         <source>Install</source>
         <translation>Zainstaluj</translation>
     </message>
@@ -703,27 +710,27 @@ Pełny ekran klasyczny - gra przysłoni cały ekran uruchamiając się w wybrane
     <message>
         <location filename="../lobby/chat_moc.ui" line="14"/>
         <source>Form</source>
-        <translation type="unfinished"></translation>
+        <translation>Okno</translation>
     </message>
     <message>
         <location filename="../lobby/chat_moc.ui" line="40"/>
         <source>Users in lobby</source>
-        <translation type="unfinished"></translation>
+        <translation>Gracze w lobby</translation>
     </message>
     <message>
         <location filename="../lobby/chat_moc.ui" line="50"/>
         <source>Global chat</source>
-        <translation type="unfinished"></translation>
+        <translation>Globalny czat</translation>
     </message>
     <message>
         <location filename="../lobby/chat_moc.ui" line="104"/>
         <source>type you message</source>
-        <translation type="unfinished"></translation>
+        <translation>napisz wiadomość</translation>
     </message>
     <message>
         <location filename="../lobby/chat_moc.ui" line="111"/>
         <source>send</source>
-        <translation type="unfinished"></translation>
+        <translation>wyślij</translation>
     </message>
 </context>
 <context>
@@ -926,87 +933,87 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
     <message>
         <location filename="../languages.cpp" line="23"/>
         <source>Czech</source>
-        <translation type="unfinished"></translation>
+        <translation>Czeski</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="24"/>
         <source>Chinese</source>
-        <translation></translation>
+        <translation>Chiński</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="25"/>
         <source>English</source>
-        <translation type="unfinished"></translation>
+        <translation>Angielski</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="26"/>
         <source>Finnish</source>
-        <translation type="unfinished"></translation>
+        <translation>Fiński</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="27"/>
         <source>French</source>
-        <translation type="unfinished"></translation>
+        <translation>Francuski</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="28"/>
         <source>German</source>
-        <translation type="unfinished"></translation>
+        <translation>Niemiecki</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="29"/>
         <source>Hungarian</source>
-        <translation type="unfinished"></translation>
+        <translation>Węgierski</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="30"/>
         <source>Italian</source>
-        <translation type="unfinished"></translation>
+        <translation>Włoski</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="31"/>
         <source>Korean</source>
-        <translation type="unfinished"></translation>
+        <translation>Koreański</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="32"/>
         <source>Polish</source>
-        <translation type="unfinished"></translation>
+        <translation>Polski</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="33"/>
         <source>Portuguese</source>
-        <translation type="unfinished"></translation>
+        <translation>Portugalski</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="34"/>
         <source>Russian</source>
-        <translation type="unfinished"></translation>
+        <translation>Rosyjski</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="35"/>
         <source>Spanish</source>
-        <translation type="unfinished"></translation>
+        <translation>Hiszpański</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="36"/>
         <source>Swedish</source>
-        <translation type="unfinished"></translation>
+        <translation>Szwedzki</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="37"/>
         <source>Turkish</source>
-        <translation type="unfinished"></translation>
+        <translation>Turecki</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="38"/>
         <source>Ukrainian</source>
-        <translation type="unfinished"></translation>
+        <translation>Ukraiński</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="39"/>
         <source>Vietnamese</source>
-        <translation type="unfinished"></translation>
+        <translation>Wietnamski</translation>
     </message>
     <message>
         <location filename="../languages.cpp" line="40"/>
@@ -1026,7 +1033,7 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
     <message>
         <location filename="../languages.cpp" line="64"/>
         <source>Auto (%1)</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
 </context>
 <context>

+ 5 - 5
launcher/translation/russian.ts

@@ -667,27 +667,27 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use
         <translation>Вступление</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="407"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="409"/>
         <source>Active</source>
         <translation>Активен</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="412"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="414"/>
         <source>Disabled</source>
         <translation>Отключен</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="413"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="415"/>
         <source>Enable</source>
         <translation>Включить</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="418"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="420"/>
         <source>Not Installed</source>
         <translation>Не установлен</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="419"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="421"/>
         <source>Install</source>
         <translation>Установить</translation>
     </message>

+ 5 - 5
launcher/translation/spanish.ts

@@ -667,27 +667,27 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use
         <translation>Idioma de los datos de Heroes III.</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="407"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="409"/>
         <source>Active</source>
         <translation>Activado</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="412"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="414"/>
         <source>Disabled</source>
         <translation>Desactivado</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="413"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="415"/>
         <source>Enable</source>
         <translation>Activar</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="418"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="420"/>
         <source>Not Installed</source>
         <translation>No Instalado</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="419"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="421"/>
         <source>Install</source>
         <translation>Instalar</translation>
     </message>

+ 5 - 5
launcher/translation/ukrainian.ts

@@ -680,27 +680,27 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use
         <translation>Вступні відео</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="407"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="409"/>
         <source>Active</source>
         <translation>Активні</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="412"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="414"/>
         <source>Disabled</source>
         <translation>Деактивований</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="413"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="415"/>
         <source>Enable</source>
         <translation>Активувати</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="418"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="420"/>
         <source>Not Installed</source>
         <translation>Не встановлено</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="419"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="421"/>
         <source>Install</source>
         <translation>Встановити</translation>
     </message>

+ 5 - 5
launcher/translation/vietnamese.ts

@@ -673,27 +673,27 @@ Toàn màn hình riêng biệt - Trò chơi chạy toàn màn hình và dùng đ
         <translation>Hiện thị giới thiệu</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="407"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="409"/>
         <source>Active</source>
         <translation>Bật</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="412"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="414"/>
         <source>Disabled</source>
         <translation>Tắt</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="413"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="415"/>
         <source>Enable</source>
         <translation>Bật</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="418"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="420"/>
         <source>Not Installed</source>
         <translation>Chưa cài đặt</translation>
     </message>
     <message>
-        <location filename="../settingsView/csettingsview_moc.cpp" line="419"/>
+        <location filename="../settingsView/csettingsview_moc.cpp" line="421"/>
         <source>Install</source>
         <translation>Cài đặt</translation>
     </message>

+ 0 - 3
lib/CArtHandler.cpp

@@ -474,9 +474,6 @@ CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode
 			// Necessary for objects added via mods that don't have any templates in H3
 			VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->getIndex())->addTemplate(templ);
 		}
-		// object does not have any templates - this is not usable object (e.g. pseudo-art like lock)
-		if(VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->getIndex())->getTemplates().empty())
-			VLC->objtypeh->removeSubObject(Obj::ARTIFACT, art->getIndex());
 	});
 
 	return art;

+ 1 - 19
lib/CPlayerState.cpp

@@ -23,25 +23,7 @@ PlayerState::PlayerState()
 	setNodeType(PLAYER);
 }
 
-PlayerState::PlayerState(PlayerState && other) noexcept:
-	CBonusSystemNode(std::move(other)),
-	color(other.color),
-	human(other.human),
-	team(other.team),
-	resources(other.resources),
-	cheated(other.cheated),
-	enteredWinningCheatCode(other.enteredWinningCheatCode),
-	enteredLosingCheatCode(other.enteredLosingCheatCode),
-	status(other.status),
-	daysWithoutCastle(other.daysWithoutCastle)
-{
-	std::swap(visitedObjects, other.visitedObjects);
-	std::swap(heroes, other.heroes);
-	std::swap(towns, other.towns);
-	std::swap(dwellings, other.dwellings);
-	std::swap(quests, other.quests);
-	std::swap(battleBonuses, other.battleBonuses);
-}
+PlayerState::PlayerState(PlayerState && other) noexcept = default;
 
 PlayerState::~PlayerState() = default;
 

+ 5 - 2
lib/MetaString.cpp

@@ -51,8 +51,11 @@ void MetaString::appendRawString(const std::string & value)
 
 void MetaString::appendTextID(const std::string & value)
 {
-	message.push_back(EMessage::APPEND_TEXTID_STRING);
-	stringsTextID.push_back(value);
+	if (!value.empty())
+	{
+		message.push_back(EMessage::APPEND_TEXTID_STRING);
+		stringsTextID.push_back(value);
+	}
 }
 
 void MetaString::appendNumber(int64_t value)

+ 7 - 0
lib/StartInfo.h

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

+ 1 - 1
lib/TurnTimerInfo.cpp

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

+ 17 - 2
lib/TurnTimerInfo.h

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

+ 1 - 7
lib/gameState/CGameState.cpp

@@ -784,7 +784,7 @@ void CGameState::initTowns()
 		CGTownInstance * vti =(elem);
 		assert(vti->town);
 
-		if(vti->getNameTranslated().empty())
+		if(vti->getNameTextID().empty())
 		{
 			size_t nameID = getRandomGenerator().nextInt(vti->getTown()->getRandomNamesCount() - 1);
 			vti->setNameTextId(vti->getTown()->getRandomNameTextID(nameID));
@@ -1797,12 +1797,6 @@ void CGameState::buildBonusSystemTree()
 	{
 		t->deserializationFix();
 	}
-	// CStackInstance <-> CCreature, CStackInstance <-> CArmedInstance, CArtifactInstance <-> CArtifact
-	// are provided on initializing / deserializing
-
-	// NOTE: calling deserializationFix() might be more correct option, but might lead to side effects
-	for (auto hero : map->heroesOnMap)
-		hero->boatDeserializationFix();
 }
 
 void CGameState::deserializationFix()

+ 6 - 0
lib/mapObjectConstructors/AObjectTypeHandler.cpp

@@ -96,6 +96,10 @@ void AObjectTypeHandler::init(const JsonNode & input)
 	else
 		aiValue = static_cast<std::optional<si32>>(input["aiValue"].Integer());
 
+	// TODO: Define properties, move them to actual object instance
+	blockVisit = input["blockVisit"].Bool();
+	removable = input["removable"].Bool();
+
 	battlefield = BattleField::NONE;
 
 	if(!input["battleground"].isNull())
@@ -120,6 +124,8 @@ void AObjectTypeHandler::preInitObject(CGObjectInstance * obj) const
 	obj->subID = subtype;
 	obj->typeName = typeName;
 	obj->subTypeName = subTypeName;
+	obj->blockVisit = blockVisit;
+	obj->removable = removable;
 }
 
 void AObjectTypeHandler::initTypeData(const JsonNode & input)

+ 3 - 0
lib/mapObjectConstructors/AObjectTypeHandler.h

@@ -43,6 +43,9 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable
 	si32 type;
 	si32 subtype;
 
+	bool blockVisit;
+	bool removable;
+
 protected:
 	void preInitObject(CGObjectInstance * obj) const;
 	virtual bool objectFilter(const CGObjectInstance * obj, std::shared_ptr<const ObjectTemplate> tmpl) const;

+ 0 - 7
lib/mapObjects/CGHeroInstance.cpp

@@ -1337,13 +1337,6 @@ std::vector<SecondarySkill> CGHeroInstance::getLevelUpProposedSecondarySkills(CR
 	if (canLearnSkill() && !none.empty() && skills.size() < 2)
 		chooseSkill(none);
 
-	if (skills.empty())
-		logGlobal->info("Selecting secondary skills: Nothing to select!");
-	if (skills.size() == 1)
-		logGlobal->info("Selecting secondary skills: %s (wisdom: %d, schools: %d)!", skills[0], skillsInfo.wisdomCounter, skillsInfo.magicSchoolCounter);
-	if (skills.size() == 2)
-		logGlobal->info("Selecting secondary skills: %s or %s (wisdom: %d, schools: %d)!", skills[0], skills[1], int(skillsInfo.wisdomCounter), int(skillsInfo.magicSchoolCounter));
-
 	return skills;
 }
 

+ 11 - 7
lib/mapObjects/CGObjectInstance.cpp

@@ -33,7 +33,8 @@ CGObjectInstance::CGObjectInstance():
 	ID(Obj::NO_OBJ),
 	subID(-1),
 	tempOwner(PlayerColor::UNFLAGGABLE),
-	blockVisit(false)
+	blockVisit(false),
+	removable(false)
 {
 }
 
@@ -173,12 +174,7 @@ void CGObjectInstance::pickRandomObject(CRandomGenerator & rand)
 
 void CGObjectInstance::initObj(CRandomGenerator & rand)
 {
-	switch(ID.toEnum())
-	{
-	case Obj::TAVERN:
-		blockVisit = true;
-		break;
-	}
+	// no-op
 }
 
 void CGObjectInstance::setProperty( ObjProperty what, ObjPropertyID identifier )
@@ -191,6 +187,7 @@ void CGObjectInstance::setProperty( ObjProperty what, ObjPropertyID identifier )
 		tempOwner = identifier.as<PlayerColor>();
 		break;
 	case ObjProperty::BLOCKVIS:
+		// Never actually used in code, but possible in ERM
 		blockVisit = identifier.getNum();
 		break;
 	case ObjProperty::ID:
@@ -327,9 +324,16 @@ bool CGObjectInstance::isVisitable() const
 
 bool CGObjectInstance::isBlockedVisitable() const
 {
+	// TODO: Read from json
 	return blockVisit;
 }
 
+bool CGObjectInstance::isRemovable() const
+{
+	// TODO: Read from json
+	return removable;
+}
+
 bool CGObjectInstance::isCoastVisitable() const
 {
 	return false;

+ 5 - 0
lib/mapObjects/CGObjectInstance.h

@@ -54,6 +54,7 @@ public:
 	int3 getSightCenter() const;
 	/// If true hero can visit this object only from neighbouring tiles and can't stand on this object
 	bool blockVisit;
+	bool removable;
 
 	PlayerColor getOwner() const override
 	{
@@ -85,6 +86,9 @@ public:
 	/// If true hero can visit this object only from neighbouring tiles and can't stand on this object
 	virtual bool isBlockedVisitable() const;
 
+	// If true, can be possibly removed from the map
+	virtual bool isRemovable() const;
+
 	/// If true this object can be visited by hero standing on the coast
 	virtual bool isCoastVisitable() const;
 
@@ -144,6 +148,7 @@ public:
 		h & id;
 		h & tempOwner;
 		h & blockVisit;
+		h & removable;
 		h & appearance;
 		//definfo is handled by map serializer
 	}

+ 5 - 0
lib/mapObjects/CGTownInstance.cpp

@@ -947,6 +947,11 @@ std::string CGTownInstance::getNameTranslated() const
 	return VLC->generaltexth->translate(nameTextId);
 }
 
+std::string CGTownInstance::getNameTextID() const
+{
+	return nameTextId;
+}
+
 void CGTownInstance::setNameTextId( const std::string & newName )
 {
 	nameTextId = newName;

+ 1 - 0
lib/mapObjects/CGTownInstance.h

@@ -137,6 +137,7 @@ public:
 	const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself
 
 	std::string getNameTranslated() const;
+	std::string getNameTextID() const;
 	void setNameTextId(const std::string & newName);
 
 	//////////////////////////////////////////////////////////////////////////

+ 3 - 1
lib/mapObjects/CQuest.h

@@ -45,7 +45,9 @@ public:
 	std::string heroName; //backup of hero name
 	HeroTypeID heroPortrait;
 
-	MetaString firstVisitText, nextVisitText, completedText;
+	MetaString firstVisitText;
+	MetaString nextVisitText;
+	MetaString completedText;
 	bool isCustomFirst;
 	bool isCustomNext;
 	bool isCustomComplete;

+ 16 - 4
lib/mapObjects/IObjectInterface.cpp

@@ -89,11 +89,23 @@ int3 IBoatGenerator::bestLocation() const
 		int3 targetTile = getObject()->visitablePos() + offset;
 		const TerrainTile *tile = getObject()->cb->getTile(targetTile, false);
 
-		if(tile) //tile is in the map
+		if(!tile)
+			continue; // tile not visible / outside the map
+
+		if(!tile->terType->isWater())
+			continue;
+
+		if (tile->blocked)
 		{
-			if(tile->terType->isWater()  &&  (!tile->blocked || tile->blockingObjects.front()->ID == Obj::BOAT)) //and is water and is not blocked or is blocked by boat
-				return targetTile;
+			bool hasBoat = false;
+			for (auto const * object : tile->blockingObjects)
+				if (object->ID == Obj::BOAT || object->ID == Obj::HERO)
+					hasBoat = true;
+
+			if (!hasBoat)
+				continue; // tile is blocked, but not by boat -> check next potential position
 		}
+		return targetTile;
 	}
 	return int3 (-1,-1,-1);
 }
@@ -112,7 +124,7 @@ IBoatGenerator::EGeneratorState IBoatGenerator::shipyardStatus() const
 	if(t->blockingObjects.empty())
 		return GOOD; //OK
 
-	if(t->blockingObjects.front()->ID == Obj::BOAT)
+	if(t->blockingObjects.front()->ID == Obj::BOAT || t->blockingObjects.front()->ID == Obj::HERO)
 		return BOAT_ALREADY_BUILT; //blocked with boat
 
 	return TILE_BLOCKED; //blocked

+ 6 - 3
lib/mapObjects/MiscObjects.cpp

@@ -739,10 +739,13 @@ void CGArtifact::pickRandomObject(CRandomGenerator & rand)
 			break;
 	}
 
-	if (ID != Obj::SPELL_SCROLL)
+	if (ID != MapObjectID::SPELL_SCROLL && ID != MapObjectID::ARTIFACT)
+	{
+		ID = MapObjectID::ARTIFACT;
+		setType(ID, subID);
+	}
+	else if (ID != MapObjectID::SPELL_SCROLL)
 		ID = MapObjectID::ARTIFACT;
-
-	setType(ID, subID);
 }
 
 void CGArtifact::initObj(CRandomGenerator & rand)

+ 2 - 7
lib/mapping/CMap.cpp

@@ -348,13 +348,8 @@ int3 CMap::guardingCreaturePosition (int3 pos) const
 	{
 		for (CGObjectInstance* obj : posTile.visitableObjects)
 		{
-			if(obj->isBlockedVisitable())
-			{
-				if (obj->ID == Obj::MONSTER) // Monster
-					return pos;
-				else
-					return int3(-1, -1, -1); //blockvis objects are not guarded by neighbouring creatures
-			}
+			if (obj->ID == Obj::MONSTER)
+				return pos;
 		}
 	}
 

+ 1 - 1
lib/mapping/MapFeaturesH3M.cpp

@@ -97,7 +97,7 @@ MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesSOD()
 	MapFormatFeaturesH3M result = getFeaturesAB();
 	result.levelSOD = true;
 
-	result.artifactsCount = 141; // + Combined artifacts
+	result.artifactsCount = 144; // + Combined artifacts + 3 unfinished artifacts (required for some maps)
 	result.artifactsBytes = 18;
 
 	result.heroesPortraitsCount = 163; // +Finneas +young Gem +young Sandro +young Yog

+ 27 - 21
lib/mapping/MapFormatH3M.cpp

@@ -171,7 +171,7 @@ void CMapLoaderH3M::readHeader()
 		identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
 	if (features.levelHOTA0)
 		identifierMapper.loadMapping(VLC->settings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS));
-	
+
 	reader->setIdentifierRemapper(identifierMapper);
 
 	// include basic mod
@@ -898,7 +898,7 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero)
 
 	if(!hero->artifactsWorn.empty() || !hero->artifactsInBackpack.empty())
 	{
-		logGlobal->debug("Hero %s at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getNameTranslated(), hero->pos.toString());
+		logGlobal->debug("Hero %d at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getHeroType().getNum(), hero->pos.toString());
 
 		hero->artifactsInBackpack.clear();
 		while(!hero->artifactsWorn.empty())
@@ -1035,7 +1035,7 @@ void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPositi
 	readMessageAndGuards(object->message, object, mapPosition);
 	Rewardable::VisitInfo vinfo;
 	auto & reward = vinfo.reward;
-	
+
 	reward.heroExperience = reader->readUInt32();
 	reward.manaDiff = reader->readInt32();
 	if(auto val = reader->readUInt8())
@@ -1052,7 +1052,7 @@ void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPositi
 	{
 		auto rId = reader->readSkill();
 		auto rVal = reader->readUInt8();
-		
+
 		reward.secondary[rId] = rVal;
 	}
 	int gart = reader->readUInt8(); //number of gained artifacts
@@ -1068,13 +1068,13 @@ void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPositi
 	{
 		auto rId = reader->readCreature();
 		auto rVal = reader->readUInt16();
-		
+
 		reward.creatures.emplace_back(rId, rVal);
 	}
-	
+
 	vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 	object->configuration.info.push_back(vinfo);
-	
+
 	reader->skipZero(8);
 }
 
@@ -1162,18 +1162,24 @@ CGObjectInstance * CMapLoaderH3M::readWitchHut(const int3 & position, std::share
 					allowedAbilities.insert(SecondarySkill(skillID));
 		}
 
-		JsonVector anyOfList;
-
-		for (auto const & skill : allowedAbilities)
+		JsonNode variable;
+		if (allowedAbilities.size() == 1)
 		{
-			JsonNode entry;
-			entry.String() = VLC->skills()->getById(skill)->getJsonKey();
-			anyOfList.push_back(entry);
+			variable.String() = VLC->skills()->getById(*allowedAbilities.begin())->getJsonKey();
+		}
+		else
+		{
+			JsonVector anyOfList;
+			for (auto const & skill : allowedAbilities)
+			{
+				JsonNode entry;
+				entry.String() = VLC->skills()->getById(skill)->getJsonKey();
+				anyOfList.push_back(entry);
+			}
+			variable["anyOf"].Vector() = anyOfList;
 		}
-		JsonNode variable;
-		variable["anyOf"].Vector() = anyOfList;
-		variable.setMeta(ModScope::scopeGame()); // list may include skills from all mods
 
+		variable.setMeta(ModScope::scopeGame()); // list may include skills from all mods
 		rewardable->configuration.presetVariable("secondarySkill", "gainedSkill", variable);
 	}
 	return object;
@@ -1846,7 +1852,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec
 			auto ps = object->getAllBonuses(Selector::type()(BonusType::PRIMARY_SKILL).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)), nullptr);
 			if(ps->size())
 			{
-				logGlobal->debug("Hero %s subID=%d has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", object->getNameTranslated(), object->subID );
+				logGlobal->debug("Hero %s has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", object->getHeroType().getNum() );
 				for(const auto & b : *ps)
 					object->removeBonus(b);
 			}
@@ -2002,7 +2008,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
 			{
 				auto rId = reader->readUInt8();
 				auto rVal = reader->readUInt8();
-				
+
 				reward.primary.at(rId) = rVal;
 				break;
 			}
@@ -2010,7 +2016,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
 			{
 				auto rId = reader->readSkill();
 				auto rVal = reader->readUInt8();
-				
+
 				reward.secondary[rId] = rVal;
 				break;
 			}
@@ -2028,7 +2034,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
 			{
 				auto rId = reader->readCreature();
 				auto rVal = reader->readUInt16();
-				
+
 				reward.creatures.emplace_back(rId, rVal);
 				break;
 			}
@@ -2037,7 +2043,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
 				assert(0);
 			}
 		}
-		
+
 		vinfo.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 		hut->configuration.info.push_back(vinfo);
 	}

+ 20 - 5
lib/mapping/MapFormatJson.cpp

@@ -158,20 +158,35 @@ namespace TriggeredEventsDetail
 			{
 				case EventCondition::HAVE_ARTIFACT:
 				case EventCondition::TRANSPORT:
-					event.objectType = ArtifactID(ArtifactID::decode(data["type"].String()));
+					if (data["type"].isNumber()) // compatibility
+						event.objectType = ArtifactID(data["type"].Integer());
+					else
+						event.objectType = ArtifactID(ArtifactID::decode(data["type"].String()));
 					break;
 				case EventCondition::HAVE_CREATURES:
-					event.objectType = CreatureID(CreatureID::decode(data["type"].String()));
+					if (data["type"].isNumber()) // compatibility
+						event.objectType = CreatureID(data["type"].Integer());
+					else
+						event.objectType = CreatureID(CreatureID::decode(data["type"].String()));
 					break;
 				case EventCondition::HAVE_RESOURCES:
-					event.objectType = GameResID(GameResID::decode(data["type"].String()));
+					if (data["type"].isNumber()) // compatibility
+						event.objectType = GameResID(data["type"].Integer());
+					else
+						event.objectType = GameResID(GameResID::decode(data["type"].String()));
 					break;
 				case EventCondition::HAVE_BUILDING:
-					event.objectType = BuildingID(BuildingID::decode(data["type"].String()));
+					if (data["type"].isNumber()) // compatibility
+						event.objectType = BuildingID(data["type"].Integer());
+					else
+						event.objectType = BuildingID(BuildingID::decode(data["type"].String()));
 					break;
 				case EventCondition::CONTROL:
 				case EventCondition::DESTROY:
-					event.objectType = MapObjectID(MapObjectID::decode(data["type"].String()));
+					if (data["type"].isNumber()) // compatibility
+						event.objectType = MapObjectID(data["type"].Integer());
+					else
+						event.objectType = MapObjectID(MapObjectID::decode(data["type"].String()));
 					break;
 			}
 

+ 4 - 1
lib/mapping/MapReaderH3M.cpp

@@ -83,7 +83,10 @@ ArtifactID MapReaderH3M::readArtifact()
 
 ArtifactID MapReaderH3M::readArtifact8()
 {
-	ArtifactID result(reader->readInt8());
+	ArtifactID result(reader->readUInt8());
+
+	if(result.getNum() == 0xff)
+		return ArtifactID::NONE;
 
 	if (result.getNum() < features.artifactsCount)
 		return remapIdentifier(result);

+ 31 - 2
lib/pathfinder/CPathfinder.cpp

@@ -24,11 +24,37 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+bool CPathfinderHelper::canMoveFromNode(const PathNodeInfo & source) const
+{
+	// we can always make the first step, even when standing on object
+	if(source.node->theNodeBefore == nullptr)
+		return true;
+
+	if (!source.nodeObject)
+		return true;
+
+	if (!source.isNodeObjectVisitable())
+		return true;
+
+	// we can always move from visitable object if hero has teleported here (e.g. went through monolith)
+	if (source.node->isTeleportAction())
+		return true;
+
+	// we can not go through teleporters since moving onto a teleport will teleport hero and may invalidate path (e.g. one-way teleport or enemy hero on other side)
+	if (dynamic_cast<const CGTeleport*>(source.nodeObject) != nullptr)
+		return false;
+
+	return true;
+}
+
 std::vector<int3> CPathfinderHelper::getNeighbourTiles(const PathNodeInfo & source) const
 {
 	std::vector<int3> neighbourTiles;
-	neighbourTiles.reserve(8);
 
+	if (!canMoveFromNode(source))
+		return neighbourTiles;
+
+	neighbourTiles.reserve(8);
 	getNeighbours(
 		*source.tile,
 		source.node->coord,
@@ -38,7 +64,7 @@ std::vector<int3> CPathfinderHelper::getNeighbourTiles(const PathNodeInfo & sour
 
 	if(source.isNodeObjectVisitable())
 	{
-		vstd::erase_if(neighbourTiles, [&](const int3 & tile) -> bool 
+		vstd::erase_if(neighbourTiles, [&](const int3 & tile) -> bool
 		{
 			return !canMoveBetween(tile, source.nodeObject->visitablePos());
 		});
@@ -136,6 +162,9 @@ void CPathfinder::calculatePaths()
 			if(neighbour->locked)
 				continue;
 
+			if (source.node->theNodeBefore && source.node->theNodeBefore->coord == neighbour->coord )
+				continue; // block U-turns
+
 			if(!hlp->isLayerAvailable(neighbour->layer))
 				continue;
 

+ 1 - 0
lib/pathfinder/CPathfinder.h

@@ -79,6 +79,7 @@ public:
 	virtual ~CPathfinderHelper();
 	void initializePatrol();
 	bool isHeroPatrolLocked() const;
+	bool canMoveFromNode(const PathNodeInfo & source) const;
 	bool isPatrolMovementAllowed(const int3 & dst) const;
 	void updateTurnInfo(const int turn = 0);
 	bool isLayerAvailable(const EPathfindingLayer & layer) const;

+ 1 - 1
lib/rmg/CMapGenOptions.cpp

@@ -313,8 +313,8 @@ void CMapGenOptions::resetPlayersMap()
 	while (players.size() < realPlayersCnt && !availableColors.empty())
 	{
 		auto color = availableColors.front();
-		setPlayerTypeForStandardPlayer(color, EPlayerType::AI);
 		players[color].setColor(color);
+		setPlayerTypeForStandardPlayer(color, EPlayerType::AI);
 		availableColors.erase(availableColors.begin());
 
 		if (vstd::contains(savedPlayerSettings, color))

+ 76 - 22
lib/rmg/RmgObject.cpp

@@ -40,7 +40,9 @@ const Area & Object::Instance::getBlockedArea() const
 	{
 		dBlockedAreaCache.assign(dObject.getBlockedPos());
 		if(dObject.isVisitable() || dBlockedAreaCache.empty())
-			dBlockedAreaCache.add(dObject.visitablePos());
+			if (!dObject.isBlockedVisitable())
+				// Do no assume blocked tile is accessible
+				dBlockedAreaCache.add(dObject.visitablePos());
 	}
 	return dBlockedAreaCache;
 }
@@ -85,9 +87,7 @@ void Object::Instance::setPosition(const int3 & position)
 	
 	dBlockedAreaCache.clear();
 	dAccessibleAreaCache.clear();
-	dParent.dAccessibleAreaCache.clear();
-	dParent.dAccessibleAreaFullCache.clear();
-	dParent.dFullAreaCache.clear();
+	dParent.clearCachedArea();
 }
 
 void Object::Instance::setPositionRaw(const int3 & position)
@@ -97,9 +97,7 @@ void Object::Instance::setPositionRaw(const int3 & position)
 		dObject.pos = dPosition + dParent.getPosition();
 		dBlockedAreaCache.clear();
 		dAccessibleAreaCache.clear();
-		dParent.dAccessibleAreaCache.clear();
-		dParent.dAccessibleAreaFullCache.clear();
-		dParent.dFullAreaCache.clear();
+		dParent.clearCachedArea();
 	}
 		
 	auto shift = position + dParent.getPosition() - dObject.pos;
@@ -141,9 +139,7 @@ void Object::Instance::clear()
 	delete &dObject;
 	dBlockedAreaCache.clear();
 	dAccessibleAreaCache.clear();
-	dParent.dAccessibleAreaCache.clear();
-	dParent.dAccessibleAreaFullCache.clear();
-	dParent.dFullAreaCache.clear();
+	dParent.clearCachedArea();
 }
 
 bool Object::Instance::isVisitableFrom(const int3 & position) const
@@ -152,6 +148,16 @@ bool Object::Instance::isVisitableFrom(const int3 & position) const
 	return dObject.appearance->isVisitableFrom(relPosition.x, relPosition.y);
 }
 
+bool Object::Instance::isBlockedVisitable() const
+{
+	return dObject.isBlockedVisitable();
+}
+
+bool Object::Instance::isRemovable() const
+{
+	return dObject.isRemovable();
+}
+
 CGObjectInstance & Object::Instance::object()
 {
 	return dObject;
@@ -205,9 +211,7 @@ void Object::addInstance(Instance & object)
 	setGuardedIfMonster(object);
 	dInstances.push_back(object);
 
-	dFullAreaCache.clear();
-	dAccessibleAreaCache.clear();
-	dAccessibleAreaFullCache.clear();
+	clearCachedArea();
 }
 
 Object::Instance & Object::addInstance(CGObjectInstance & object)
@@ -215,9 +219,7 @@ Object::Instance & Object::addInstance(CGObjectInstance & object)
 	dInstances.emplace_back(*this, object);
 	setGuardedIfMonster(dInstances.back());
 
-	dFullAreaCache.clear();
-	dAccessibleAreaCache.clear();
-	dAccessibleAreaFullCache.clear();
+	clearCachedArea();
 	return dInstances.back();
 }
 
@@ -226,9 +228,7 @@ Object::Instance & Object::addInstance(CGObjectInstance & object, const int3 & p
 	dInstances.emplace_back(*this, object, position);
 	setGuardedIfMonster(dInstances.back());
 
-	dFullAreaCache.clear();
-	dAccessibleAreaCache.clear();
-	dAccessibleAreaFullCache.clear();
+	clearCachedArea();
 	return dInstances.back();
 }
 
@@ -270,10 +270,56 @@ const rmg::Area & Object::getAccessibleArea(bool exceptLast) const
 		return dAccessibleAreaFullCache;
 }
 
+const rmg::Area & Object::getBlockVisitableArea() const
+{
+	if(dInstances.empty())
+		return dBlockVisitableCache;
+
+	for(const auto & i : dInstances)
+	{
+		// FIXME: Account for blockvis objects with multiple visitable tiles
+		if (i.isBlockedVisitable())
+			dBlockVisitableCache.add(i.getVisitablePosition());
+	}
+
+	return dBlockVisitableCache;
+}
+
+const rmg::Area & Object::getRemovableArea() const
+{
+	if(dInstances.empty())
+		return dRemovableAreaCache;
+
+	for(const auto & i : dInstances)
+	{
+		if (i.isRemovable())
+			dRemovableAreaCache.unite(i.getBlockedArea());
+	}
+
+	return dRemovableAreaCache;
+}
+
+const rmg::Area Object::getEntrableArea() const
+{
+	// Calculate Area that hero can freely pass
+
+	// Do not use blockVisitTiles, unless they belong to removable objects (resources etc.)
+	// area = accessibleArea - (blockVisitableArea - removableArea)
+
+	rmg::Area entrableArea = getAccessibleArea();
+	rmg::Area blockVisitableArea = getBlockVisitableArea();
+	blockVisitableArea.subtract(getRemovableArea());
+	entrableArea.subtract(blockVisitableArea);
+
+	return entrableArea;
+}
+
 void Object::setPosition(const int3 & position)
 {
 	dAccessibleAreaCache.translate(position - dPosition);
 	dAccessibleAreaFullCache.translate(position - dPosition);
+	dBlockVisitableCache.translate(position - dPosition);
+	dRemovableAreaCache.translate(position - dPosition);
 	dFullAreaCache.translate(position - dPosition);
 	
 	dPosition = position;
@@ -374,14 +420,22 @@ void Object::finalize(RmgMap & map, CRandomGenerator & rng)
 	}
 }
 
+void Object::clearCachedArea() const
+{
+	dFullAreaCache.clear();
+	dAccessibleAreaCache.clear();
+	dAccessibleAreaFullCache.clear();
+	dBlockVisitableCache.clear();
+	dRemovableAreaCache.clear();
+}
+
 void Object::clear()
 {
 	for(auto & instance : dInstances)
 		instance.clear();
 	dInstances.clear();
-	dFullAreaCache.clear();
-	dAccessibleAreaCache.clear();
-	dAccessibleAreaFullCache.clear();
+
+	clearCachedArea();
 }
  
 

+ 8 - 0
lib/rmg/RmgObject.h

@@ -35,6 +35,8 @@ public:
 		
 		int3 getVisitablePosition() const;
 		bool isVisitableFrom(const int3 & tile) const;
+		bool isBlockedVisitable() const;
+		bool isRemovable() const;
 		const Area & getAccessibleArea() const;
 		void setTemplate(TerrainId terrain, CRandomGenerator &); //cache invalidation
 		void setAnyTemplate(CRandomGenerator &); //cache invalidation
@@ -71,6 +73,9 @@ public:
 	
 	int3 getVisitablePosition() const;
 	const Area & getAccessibleArea(bool exceptLast = false) const;
+	const Area & getBlockVisitableArea() const;
+	const Area & getRemovableArea() const;
+	const Area getEntrableArea() const;
 	
 	const int3 & getPosition() const;
 	void setPosition(const int3 & position);
@@ -83,12 +88,15 @@ public:
 	void setGuardedIfMonster(const Instance & object);
 	
 	void finalize(RmgMap & map, CRandomGenerator &);
+	void clearCachedArea() const;
 	void clear();
 	
 private:
 	std::list<Instance> dInstances;
 	mutable Area dFullAreaCache;
 	mutable Area dAccessibleAreaCache, dAccessibleAreaFullCache;
+	mutable Area dBlockVisitableCache;
+	mutable Area dRemovableAreaCache;
 	int3 dPosition;
 	ui32 dStrength;
 	bool guarded;

+ 2 - 2
lib/rmg/modificators/ObjectDistributor.cpp

@@ -42,8 +42,6 @@ void ObjectDistributor::init()
 
 void ObjectDistributor::distributeLimitedObjects()
 {
-	//FIXME: Must be called after TerrainPainter::process()
-
 	ObjectInfo oi;
 	auto zones = map.getZones();
 
@@ -77,6 +75,8 @@ void ObjectDistributor::distributeLimitedObjects()
 
 					auto rmgInfo = handler->getRMGInfo();
 
+					// FIXME: Random order of distribution
+					RandomGeneratorUtil::randomShuffle(matchingZones, zone.getRand());
 					for (auto& zone : matchingZones)
 					{
 						oi.generateObject = [primaryID, secondaryID]() -> CGObjectInstance *

+ 19 - 3
lib/rmg/modificators/ObjectManager.cpp

@@ -546,8 +546,12 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD
 		objects.push_back(&instance->object());
 		if(auto * m = zone.getModificator<RoadPlacer>())
 		{
-			//FIXME: Objects that can be removed, can be trespassed. Does not include Corpse
-			if(instance->object().appearance->isVisitableFromTop())
+			if (instance->object().blockVisit && !instance->object().removable)
+			{
+				//Cannot be trespassed (Corpse)
+				continue;
+			}
+			else if(instance->object().appearance->isVisitableFromTop())
 				m->areaForRoads().add(instance->getVisitablePosition());
 			else
 			{
@@ -664,7 +668,19 @@ bool ObjectManager::addGuard(rmg::Object & object, si32 strength, bool zoneGuard
 	if(!guard)
 		return false;
 	
-	rmg::Area visitablePos({object.getVisitablePosition()});
+	// Prefer non-blocking tiles, if any
+	auto entrableTiles = object.getEntrableArea().getTiles();
+	int3 entrableTile(-1, -1, -1);
+	if (entrableTiles.empty())
+	{
+		entrableTile = object.getVisitablePosition();
+	}
+	else
+	{
+		entrableTile = *RandomGeneratorUtil::nextItem(entrableTiles, zone.getRand());
+	}
+
+	rmg::Area visitablePos({entrableTile});
 	visitablePos.unite(visitablePos.getBorderOutside());
 	
 	auto accessibleArea = object.getAccessibleArea();

+ 1 - 1
lib/rmg/modificators/RoadPlacer.cpp

@@ -85,7 +85,7 @@ bool RoadPlacer::createRoad(const int3 & dst)
 			{
 				if(areaIsolated().contains(dst) || areaIsolated().contains(src))
 				{
-					return 1e30;
+					return 1e12;
 				}
 			}
 			else

+ 36 - 17
lib/rmg/modificators/TreasurePlacer.cpp

@@ -625,20 +625,31 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 	for(const auto & oi : treasureInfos)
 	{
 		auto blockedArea = rmgObject.getArea();
-		auto accessibleArea = rmgObject.getAccessibleArea();
+		auto entrableArea = rmgObject.getEntrableArea();
+		
 		if(rmgObject.instances().empty())
-			accessibleArea.add(int3());
+			entrableArea.add(int3());
 		
 		auto * object = oi->generateObject();
 		if(oi->templates.empty())
 			continue;
 		
 		object->appearance = *RandomGeneratorUtil::nextItem(oi->templates, zone.getRand());
+
+		auto blockingIssue = object->isBlockedVisitable() && !object->isRemovable();
+		if (blockingIssue)
+		{
+			// Do not place next to another such object (Corpse issue)
+			// Calculate this before instance is added to rmgObject
+			auto blockVisitProximity = rmgObject.getBlockVisitableArea().getBorderOutside();
+			entrableArea.subtract(blockVisitProximity);
+		}
+
 		auto & instance = rmgObject.addInstance(*object);
 
 		do
 		{
-			if(accessibleArea.empty())
+			if(entrableArea.empty())
 			{
 				//fail - fallback
 				rmgObject.clear();
@@ -649,12 +660,14 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 			if(densePlacement)
 			{
 				int bestPositionsWeight = std::numeric_limits<int>::max();
-				for(const auto & t : accessibleArea.getTilesVector())
+				for(const auto & t : entrableArea.getTilesVector())
 				{
 					instance.setPosition(t);
-					int w = rmgObject.getAccessibleArea().getTilesVector().size();
-					if(w < bestPositionsWeight)
+					int w = rmgObject.getEntrableArea().getTilesVector().size();
+
+					if(w && w < bestPositionsWeight)
 					{
+						// Minimum 1 position must be entrable
 						bestPositions.clear();
 						bestPositions.push_back(t);
 						bestPositionsWeight = w;
@@ -664,10 +677,12 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 						bestPositions.push_back(t);
 					}
 				}
+
 			}
-			else
+
+			if (bestPositions.empty())
 			{
-				bestPositions = accessibleArea.getTilesVector();
+				bestPositions = entrableArea.getTilesVector();
 			}
 			
 			int3 nextPos = *RandomGeneratorUtil::nextItem(bestPositions, zone.getRand());
@@ -676,20 +691,19 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 			auto instanceAccessibleArea = instance.getAccessibleArea();
 			if(instance.getBlockedArea().getTilesVector().size() == 1)
 			{
-				if(instance.object().appearance->isVisitableFromTop() && instance.object().ID != Obj::CORPSE)
+				if(instance.object().appearance->isVisitableFromTop() && !instance.object().isBlockedVisitable())
 					instanceAccessibleArea.add(instance.getVisitablePosition());
 			}
 			
 			//first object is good
 			if(rmgObject.instances().size() == 1)
 				break;
-			
-			//condition for good position
-			if(!blockedArea.overlap(instance.getBlockedArea()) && accessibleArea.overlap(instanceAccessibleArea))
+
+			if(!blockedArea.overlap(instance.getBlockedArea()) && entrableArea.overlap(instanceAccessibleArea))
 				break;
-			
+
 			//fail - new position
-			accessibleArea.erase(nextPos);
+			entrableArea.erase(nextPos);
 		} while(true);
 	}
 	return rmgObject;
@@ -822,13 +836,18 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
 
 			int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo* oi) {return v + oi->value; });
 
-			for (ui32 attempt = 0; attempt <= 2; attempt++)
+			const ui32 maxPileGenerationAttemps = 2;
+			for (ui32 attempt = 0; attempt <= maxPileGenerationAttemps; attempt++)
 			{
 				auto rmgObject = constructTreasurePile(treasurePileInfos, attempt == maxAttempts);
 
-				if (rmgObject.instances().empty()) //handle incorrect placement
+				if (rmgObject.instances().empty())
 				{
-					restoreZoneLimits(treasurePileInfos);
+					// Restore once if all attemps failed
+					if (attempt == (maxPileGenerationAttemps - 1))
+					{
+						restoreZoneLimits(treasurePileInfos);
+					}
 					continue;
 				}
 

部分文件因为文件数量过多而无法显示